mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-11 00:50:47 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bee14b7de0 | ||
|
|
6c96cdbec1 | ||
|
|
339299e08e | ||
|
|
1b1d043d50 | ||
|
|
c3a22333f7 | ||
|
|
ea057f14dc | ||
|
|
0da968fd5a | ||
|
|
7ab3403bca | ||
|
|
9a5ad3ec26 | ||
|
|
8f06776b53 | ||
|
|
248f9cce51 | ||
|
|
9316eb0a53 | ||
|
|
a01edc13cb | ||
|
|
2b2c305ccb |
+5
-1
@@ -44,6 +44,7 @@ Thumbs.db
|
||||
|
||||
# Ignore all Go files in the components directory
|
||||
components/*.go
|
||||
!components/components.go
|
||||
|
||||
# Ignore the data directory
|
||||
data/
|
||||
@@ -57,4 +58,7 @@ configs/
|
||||
# Ignore Dirs
|
||||
source/
|
||||
destination/
|
||||
archive/
|
||||
archive/
|
||||
|
||||
# Ignore binaries
|
||||
gomft
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// Package components contains the UI components for the GoMFT application.
|
||||
// This file serves as a marker for the components package to ensure it's properly recognized by Go.
|
||||
package components
|
||||
@@ -38,6 +38,11 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
sourceDomain := ""
|
||||
// FTP fields
|
||||
sourcePassiveMode := true
|
||||
// OneDrive and Google Drive fields
|
||||
sourceClientId := ""
|
||||
sourceClientSecret := ""
|
||||
sourceDriveId := ""
|
||||
sourceTeamDrive := ""
|
||||
filePattern := "*"
|
||||
outputPattern := ""
|
||||
destinationType := "local"
|
||||
@@ -58,6 +63,11 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
destDomain := ""
|
||||
// FTP fields
|
||||
destPassiveMode := true
|
||||
// OneDrive and Google Drive fields
|
||||
destClientId := ""
|
||||
destClientSecret := ""
|
||||
destDriveId := ""
|
||||
destTeamDrive := ""
|
||||
archivePath := ""
|
||||
archiveEnabled := false
|
||||
deleteAfterTransfer := false
|
||||
@@ -83,6 +93,11 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
sourceDomain = config.SourceDomain
|
||||
// FTP fields
|
||||
sourcePassiveMode = config.SourcePassiveMode
|
||||
// OneDrive and Google Drive fields
|
||||
sourceClientId = config.SourceClientID
|
||||
sourceClientSecret = config.SourceClientSecret
|
||||
sourceDriveId = config.SourceDriveID
|
||||
sourceTeamDrive = config.SourceTeamDrive
|
||||
filePattern = config.FilePattern
|
||||
outputPattern = config.OutputPattern
|
||||
destinationType = config.DestinationType
|
||||
@@ -103,6 +118,11 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
destDomain = config.DestDomain
|
||||
// FTP fields
|
||||
destPassiveMode = config.DestPassiveMode
|
||||
// OneDrive and Google Drive fields
|
||||
destClientId = config.DestClientID
|
||||
destClientSecret = config.DestClientSecret
|
||||
destDriveId = config.DestDriveID
|
||||
destTeamDrive = config.DestTeamDrive
|
||||
archivePath = config.ArchivePath
|
||||
archiveEnabled = config.ArchiveEnabled
|
||||
deleteAfterTransfer = config.DeleteAfterTransfer
|
||||
@@ -126,6 +146,10 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
sourceShare: '%s',
|
||||
sourceDomain: '%s',
|
||||
sourcePassiveMode: %v,
|
||||
sourceClientId: '%s',
|
||||
sourceClientSecret: '%s',
|
||||
sourceDriveId: '%s',
|
||||
sourceTeamDrive: '%s',
|
||||
filePattern: '%s',
|
||||
outputPattern: '%s',
|
||||
destinationType: '%s',
|
||||
@@ -146,12 +170,18 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
destDomain: '%s',
|
||||
// FTP fields
|
||||
destPassiveMode: %v,
|
||||
// OneDrive and Google Drive fields
|
||||
destClientId: '%s',
|
||||
destClientSecret: '%s',
|
||||
destDriveId: '%s',
|
||||
destTeamDrive: '%s',
|
||||
// Existing fields
|
||||
archivePath: '%s',
|
||||
archiveEnabled: %v,
|
||||
deleteAfterTransfer: %v,
|
||||
rcloneFlags: '%s',
|
||||
loading: false,
|
||||
|
||||
validate() {
|
||||
if (this.sourceType === 'sftp') {
|
||||
if (!this.sourceHost || !this.sourceUser || (!this.sourcePassword && !this.sourceKeyFile)) {
|
||||
@@ -177,6 +207,22 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
if (!this.sourceHost || !this.sourceUser || !this.sourcePassword) {
|
||||
return false;
|
||||
}
|
||||
} else if (this.sourceType === 'webdav') {
|
||||
if (!this.sourceHost || !this.sourceUser || !this.sourcePassword) {
|
||||
return false;
|
||||
}
|
||||
} else if (this.sourceType === 'nextcloud') {
|
||||
if (!this.sourceHost || !this.sourceUser || !this.sourcePassword) {
|
||||
return false;
|
||||
}
|
||||
} else if (this.sourceType === 'onedrive') {
|
||||
if (!this.sourceClientId || !this.sourceClientSecret) {
|
||||
return false;
|
||||
}
|
||||
} else if (this.sourceType === 'google_drive') {
|
||||
if (!this.sourceClientId || !this.sourceClientSecret) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.destinationType === 'sftp') {
|
||||
@@ -203,15 +249,33 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
if (!this.destHost || !this.destUser || !this.destPassword) {
|
||||
return false;
|
||||
}
|
||||
} else if (this.destinationType === 'webdav') {
|
||||
if (!this.destHost || !this.destUser || !this.destPassword) {
|
||||
return false;
|
||||
}
|
||||
} else if (this.destinationType === 'nextcloud') {
|
||||
if (!this.destHost || !this.destUser || !this.destPassword) {
|
||||
return false;
|
||||
}
|
||||
} else if (this.destinationType === 'onedrive') {
|
||||
if (!this.destClientId || !this.destClientSecret) {
|
||||
return false;
|
||||
}
|
||||
} else if (this.destinationType === 'google_drive') {
|
||||
if (!this.destClientId || !this.destClientSecret) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this.name && this.sourcePath && this.filePattern && this.destinationPath && (!this.archiveEnabled || this.archivePath);
|
||||
}
|
||||
}`, name, sourceType, sourcePath, sourceHost, sourcePort, sourceUser, sourcePassword, sourceKeyFile,
|
||||
sourceBucket, sourceRegion, sourceAccessKey, sourceSecretKey, sourceEndpoint,
|
||||
sourceShare, sourceDomain, sourcePassiveMode,
|
||||
sourceClientId, sourceClientSecret, sourceDriveId, sourceTeamDrive,
|
||||
filePattern, outputPattern, destinationType, destinationPath, destHost, destPort, destUser, destPassword, destKeyFile,
|
||||
destBucket, destRegion, destAccessKey, destSecretKey, destEndpoint,
|
||||
destShare, destDomain, destPassiveMode,
|
||||
destClientId, destClientSecret, destDriveId, destTeamDrive,
|
||||
archivePath, archiveEnabled, deleteAfterTransfer, rcloneFlags)
|
||||
}
|
||||
|
||||
@@ -284,6 +348,10 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
<option value="b2">Backblaze B2</option>
|
||||
<option value="smb">SMB</option>
|
||||
<option value="ftp">FTP</option>
|
||||
<option value="webdav">WebDAV</option>
|
||||
<option value="nextcloud">NextCloud</option>
|
||||
//<option value="onedrive">OneDrive</option>
|
||||
//<option value="google_drive">Google Drive</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -728,6 +796,268 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-if="sourceType === 'webdav'">
|
||||
<div class="sm:col-span-6 space-y-6">
|
||||
<div class="sm:col-span-4">
|
||||
<label for="source_host" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">WebDAV URL</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-globe text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="source_host"
|
||||
id="source_host"
|
||||
x-model="sourceHost"
|
||||
required
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
placeholder="https://webdav.example.com/remote.php/webdav/"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-4">
|
||||
<label for="source_user" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Username</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-user text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="source_user"
|
||||
id="source_user"
|
||||
x-model="sourceUser"
|
||||
required
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-4">
|
||||
<label for="source_password" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Password</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-key text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
name="source_password"
|
||||
id="source_password"
|
||||
x-model="sourcePassword"
|
||||
required
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
placeholder="Password"/>
|
||||
</div>
|
||||
<input type="hidden" name="source_password" :value="sourcePassword"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-if="sourceType === 'nextcloud'">
|
||||
<div class="sm:col-span-6 space-y-6">
|
||||
<div class="sm:col-span-4">
|
||||
<label for="source_host" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">NextCloud URL</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-cloud text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="source_host"
|
||||
id="source_host"
|
||||
x-model="sourceHost"
|
||||
required
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
placeholder="https://nextcloud.example.com"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-4">
|
||||
<label for="source_user" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Username</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-user text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="source_user"
|
||||
id="source_user"
|
||||
x-model="sourceUser"
|
||||
required
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-4">
|
||||
<label for="source_password" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Password or App Password</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-key text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
name="source_password"
|
||||
id="source_password"
|
||||
x-model="sourcePassword"
|
||||
required
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
placeholder="Password"/>
|
||||
</div>
|
||||
<input type="hidden" name="source_password" :value="sourcePassword"/>
|
||||
<p class="mt-2 text-sm text-secondary-500 dark:text-secondary-400">Using an app password is recommended for security</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
// <template x-if="sourceType === 'onedrive'">
|
||||
// <div class="sm:col-span-6 space-y-6">
|
||||
// <div class="relative p-4 border border-yellow-300 bg-yellow-50 dark:bg-yellow-900/30 dark:border-yellow-700 rounded-lg mb-4">
|
||||
// <div class="flex">
|
||||
// <div class="flex-shrink-0">
|
||||
// <i class="fas fa-info-circle text-yellow-400"></i>
|
||||
// </div>
|
||||
// <div class="ml-3">
|
||||
// <h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-300">OneDrive Authentication</h3>
|
||||
// <div class="mt-2 text-sm text-yellow-700 dark:text-yellow-400">
|
||||
// <p>OneDrive requires OAuth authentication. You'll need to:</p>
|
||||
// <ol class="list-decimal ml-4 mt-1 space-y-1">
|
||||
// <li>Register an application in the Microsoft Azure portal</li>
|
||||
// <li>Add redirect URI: http://localhost:53682/</li>
|
||||
// <li>Save the application details below</li>
|
||||
// </ol>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div class="sm:col-span-4">
|
||||
// <label for="source_client_id" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Client ID</label>
|
||||
// <div class="relative">
|
||||
// <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
// <i class="fas fa-id-card text-secondary-400 dark:text-secondary-600"></i>
|
||||
// </div>
|
||||
// <input
|
||||
// type="text"
|
||||
// name="source_client_id"
|
||||
// id="source_client_id"
|
||||
// x-model="sourceClientId"
|
||||
// required
|
||||
// class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
// placeholder="Azure application client ID"/>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div class="sm:col-span-4">
|
||||
// <label for="source_client_secret" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Client Secret</label>
|
||||
// <div class="relative">
|
||||
// <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
// <i class="fas fa-key text-secondary-400 dark:text-secondary-600"></i>
|
||||
// </div>
|
||||
// <input
|
||||
// type="password"
|
||||
// name="source_client_secret"
|
||||
// id="source_client_secret"
|
||||
// x-model="sourceClientSecret"
|
||||
// required
|
||||
// class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
// placeholder="Azure application client secret"/>
|
||||
// </div>
|
||||
// <input type="hidden" name="source_client_secret" :value="sourceClientSecret"/>
|
||||
// </div>
|
||||
|
||||
// <div class="sm:col-span-4">
|
||||
// <label for="source_drive_id" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Drive ID (Optional)</label>
|
||||
// <div class="relative">
|
||||
// <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
// <i class="fas fa-hdd text-secondary-400 dark:text-secondary-600"></i>
|
||||
// </div>
|
||||
// <input
|
||||
// type="text"
|
||||
// name="source_drive_id"
|
||||
// id="source_drive_id"
|
||||
// x-model="sourceDriveId"
|
||||
// class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
// placeholder="OneDrive drive ID (leave empty for default)"/>
|
||||
// </div>
|
||||
// <p class="mt-2 text-sm text-secondary-500 dark:text-secondary-400">For shared or business drives</p>
|
||||
// </div>
|
||||
// </div>
|
||||
// </template>
|
||||
|
||||
// <template x-if="sourceType === 'google_drive'">
|
||||
// <div class="sm:col-span-6 space-y-6">
|
||||
// <div class="relative p-4 border border-yellow-300 bg-yellow-50 dark:bg-yellow-900/30 dark:border-yellow-700 rounded-lg mb-4">
|
||||
// <div class="flex">
|
||||
// <div class="flex-shrink-0">
|
||||
// <i class="fas fa-info-circle text-yellow-400"></i>
|
||||
// </div>
|
||||
// <div class="ml-3">
|
||||
// <h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-300">Google Drive Authentication</h3>
|
||||
// <div class="mt-2 text-sm text-yellow-700 dark:text-yellow-400">
|
||||
// <p>Google Drive requires OAuth authentication. You'll need to:</p>
|
||||
// <ol class="list-decimal ml-4 mt-1 space-y-1">
|
||||
// <li>Create a project in Google Cloud Console</li>
|
||||
// <li>Enable Google Drive API</li>
|
||||
// <li>Create OAuth credentials</li>
|
||||
// <li>Add redirect URI: http://localhost:53682/</li>
|
||||
// <li>Save the client details below</li>
|
||||
// </ol>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div class="sm:col-span-4">
|
||||
// <label for="source_client_id" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Client ID</label>
|
||||
// <div class="relative">
|
||||
// <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
// <i class="fas fa-id-card text-secondary-400 dark:text-secondary-600"></i>
|
||||
// </div>
|
||||
// <input
|
||||
// type="text"
|
||||
// name="source_client_id"
|
||||
// id="source_client_id"
|
||||
// x-model="sourceClientId"
|
||||
// required
|
||||
// class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
// placeholder="Google OAuth client ID"/>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div class="sm:col-span-4">
|
||||
// <label for="source_client_secret" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Client Secret</label>
|
||||
// <div class="relative">
|
||||
// <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
// <i class="fas fa-key text-secondary-400 dark:text-secondary-600"></i>
|
||||
// </div>
|
||||
// <input
|
||||
// type="password"
|
||||
// name="source_client_secret"
|
||||
// id="source_client_secret"
|
||||
// x-model="sourceClientSecret"
|
||||
// required
|
||||
// class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
// placeholder="Google OAuth client secret"/>
|
||||
// </div>
|
||||
// <input type="hidden" name="source_client_secret" :value="sourceClientSecret"/>
|
||||
// </div>
|
||||
|
||||
// <div class="sm:col-span-4">
|
||||
// <label for="source_team_drive" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Team Drive ID (Optional)</label>
|
||||
// <div class="relative">
|
||||
// <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
// <i class="fas fa-users text-secondary-400 dark:text-secondary-600"></i>
|
||||
// </div>
|
||||
// <input
|
||||
// type="text"
|
||||
// name="source_team_drive"
|
||||
// id="source_team_drive"
|
||||
// x-model="sourceTeamDrive"
|
||||
// class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
// placeholder="Team Drive ID (leave empty for personal drive)"/>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </template>
|
||||
|
||||
<div class="sm:col-span-6">
|
||||
<label for="source_path" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Source Path</label>
|
||||
<div class="relative">
|
||||
@@ -814,6 +1144,10 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
<option value="b2">Backblaze B2</option>
|
||||
<option value="smb">SMB</option>
|
||||
<option value="ftp">FTP</option>
|
||||
<option value="webdav">WebDAV</option>
|
||||
<option value="nextcloud">NextCloud</option>
|
||||
// <option value="onedrive">OneDrive</option>
|
||||
// <option value="google_drive">Google Drive</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1258,15 +1592,277 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-if="destinationType === 'webdav'">
|
||||
<div class="sm:col-span-6 space-y-6">
|
||||
<div class="sm:col-span-4">
|
||||
<label for="dest_host" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">WebDAV URL</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-globe text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="dest_host"
|
||||
id="dest_host"
|
||||
x-model="destHost"
|
||||
required
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
placeholder="https://webdav.example.com/remote.php/webdav/"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-4">
|
||||
<label for="dest_user" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Username</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-user text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="dest_user"
|
||||
id="dest_user"
|
||||
x-model="destUser"
|
||||
required
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-4">
|
||||
<label for="dest_password" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Password</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-key text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
name="dest_password"
|
||||
id="dest_password"
|
||||
x-model="destPassword"
|
||||
required
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
placeholder="Password"/>
|
||||
</div>
|
||||
<input type="hidden" name="dest_password" :value="destPassword"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-if="destinationType === 'nextcloud'">
|
||||
<div class="sm:col-span-6 space-y-6">
|
||||
<div class="sm:col-span-4">
|
||||
<label for="dest_host" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">NextCloud URL</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-cloud text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="dest_host"
|
||||
id="dest_host"
|
||||
x-model="destHost"
|
||||
required
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
placeholder="https://nextcloud.example.com"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-4">
|
||||
<label for="dest_user" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Username</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-user text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="dest_user"
|
||||
id="dest_user"
|
||||
x-model="destUser"
|
||||
required
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-4">
|
||||
<label for="dest_password" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Password or App Password</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-key text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
name="dest_password"
|
||||
id="dest_password"
|
||||
x-model="destPassword"
|
||||
required
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
placeholder="Password"/>
|
||||
</div>
|
||||
<input type="hidden" name="dest_password" :value="destPassword"/>
|
||||
<p class="mt-2 text-sm text-secondary-500 dark:text-secondary-400">Using an app password is recommended for security</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
// <template x-if="destinationType === 'onedrive'">
|
||||
// <div class="sm:col-span-6 space-y-6">
|
||||
// <div class="relative p-4 border border-yellow-300 bg-yellow-50 dark:bg-yellow-900/30 dark:border-yellow-700 rounded-lg mb-4">
|
||||
// <div class="flex">
|
||||
// <div class="flex-shrink-0">
|
||||
// <i class="fas fa-info-circle text-yellow-400"></i>
|
||||
// </div>
|
||||
// <div class="ml-3">
|
||||
// <h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-300">OneDrive Authentication</h3>
|
||||
// <div class="mt-2 text-sm text-yellow-700 dark:text-yellow-400">
|
||||
// <p>OneDrive requires OAuth authentication. You'll need to:</p>
|
||||
// <ol class="list-decimal ml-4 mt-1 space-y-1">
|
||||
// <li>Register an application in the Microsoft Azure portal</li>
|
||||
// <li>Add redirect URI: http://localhost:53682/</li>
|
||||
// <li>Save the application details below</li>
|
||||
// </ol>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div class="sm:col-span-4">
|
||||
// <label for="dest_client_id" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Client ID</label>
|
||||
// <div class="relative">
|
||||
// <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
// <i class="fas fa-id-card text-secondary-400 dark:text-secondary-600"></i>
|
||||
// </div>
|
||||
// <input
|
||||
// type="text"
|
||||
// name="dest_client_id"
|
||||
// id="dest_client_id"
|
||||
// x-model="destClientId"
|
||||
// required
|
||||
// class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
// placeholder="Azure application client ID"/>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div class="sm:col-span-4">
|
||||
// <label for="dest_client_secret" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Client Secret</label>
|
||||
// <div class="relative">
|
||||
// <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
// <i class="fas fa-key text-secondary-400 dark:text-secondary-600"></i>
|
||||
// </div>
|
||||
// <input
|
||||
// type="password"
|
||||
// name="dest_client_secret"
|
||||
// id="dest_client_secret"
|
||||
// x-model="destClientSecret"
|
||||
// required
|
||||
// class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
// placeholder="Azure application client secret"/>
|
||||
// </div>
|
||||
// <input type="hidden" name="dest_client_secret" :value="destClientSecret"/>
|
||||
// </div>
|
||||
|
||||
// <div class="sm:col-span-4">
|
||||
// <label for="dest_drive_id" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Drive ID (Optional)</label>
|
||||
// <div class="relative">
|
||||
// <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
// <i class="fas fa-hdd text-secondary-400 dark:text-secondary-600"></i>
|
||||
// </div>
|
||||
// <input
|
||||
// type="text"
|
||||
// name="dest_drive_id"
|
||||
// id="dest_drive_id"
|
||||
// x-model="destDriveId"
|
||||
// class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
// placeholder="OneDrive drive ID (leave empty for default)"/>
|
||||
// </div>
|
||||
// <p class="mt-2 text-sm text-secondary-500 dark:text-secondary-400">For shared or business drives</p>
|
||||
// </div>
|
||||
// </div>
|
||||
// </template>
|
||||
|
||||
// <template x-if="destinationType === 'google_drive'">
|
||||
// <div class="sm:col-span-6 space-y-6">
|
||||
// <div class="relative p-4 border border-yellow-300 bg-yellow-50 dark:bg-yellow-900/30 dark:border-yellow-700 rounded-lg mb-4">
|
||||
// <div class="flex">
|
||||
// <div class="flex-shrink-0">
|
||||
// <i class="fas fa-info-circle text-yellow-400"></i>
|
||||
// </div>
|
||||
// <div class="ml-3">
|
||||
// <h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-300">Google Drive Authentication</h3>
|
||||
// <div class="mt-2 text-sm text-yellow-700 dark:text-yellow-400">
|
||||
// <p>Google Drive requires OAuth authentication. You'll need to:</p>
|
||||
// <ol class="list-decimal ml-4 mt-1 space-y-1">
|
||||
// <li>Create a project in Google Cloud Console</li>
|
||||
// <li>Enable Google Drive API</li>
|
||||
// <li>Create OAuth credentials</li>
|
||||
// <li>Add redirect URI: http://localhost:53682/</li>
|
||||
// <li>Save the client details below</li>
|
||||
// </ol>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div class="sm:col-span-4">
|
||||
// <label for="dest_client_id" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Client ID</label>
|
||||
// <div class="relative">
|
||||
// <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
// <i class="fas fa-id-card text-secondary-400 dark:text-secondary-600"></i>
|
||||
// </div>
|
||||
// <input
|
||||
// type="text"
|
||||
// name="dest_client_id"
|
||||
// id="dest_client_id"
|
||||
// x-model="destClientId"
|
||||
// required
|
||||
// class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
// placeholder="Google OAuth client ID"/>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div class="sm:col-span-4">
|
||||
// <label for="dest_client_secret" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Client Secret</label>
|
||||
// <div class="relative">
|
||||
// <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
// <i class="fas fa-key text-secondary-400 dark:text-secondary-600"></i>
|
||||
// </div>
|
||||
// <input
|
||||
// type="password"
|
||||
// name="dest_client_secret"
|
||||
// id="dest_client_secret"
|
||||
// x-model="destClientSecret"
|
||||
// required
|
||||
// class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
// placeholder="Google OAuth client secret"/>
|
||||
// </div>
|
||||
// <input type="hidden" name="dest_client_secret" :value="destClientSecret"/>
|
||||
// </div>
|
||||
|
||||
// <div class="sm:col-span-4">
|
||||
// <label for="dest_team_drive" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Team Drive ID (Optional)</label>
|
||||
// <div class="relative">
|
||||
// <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
// <i class="fas fa-users text-secondary-400 dark:text-secondary-600"></i>
|
||||
// </div>
|
||||
// <input
|
||||
// type="text"
|
||||
// name="dest_team_drive"
|
||||
// id="dest_team_drive"
|
||||
// x-model="destTeamDrive"
|
||||
// class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
// placeholder="Team Drive ID (leave empty for personal drive)"/>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </template>
|
||||
|
||||
<div class="sm:col-span-6">
|
||||
<label for="destination_path" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Destination Path</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<i class="fas fa-folder-open text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="destination_path"
|
||||
<input
|
||||
type="text"
|
||||
name="destination_path"
|
||||
id="destination_path"
|
||||
x-model="destinationPath"
|
||||
required
|
||||
@@ -1302,7 +1898,6 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
placeholder="/path/to/archive"/>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-secondary-500 dark:text-secondary-400">Location to move files after successful transfer</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1339,7 +1934,6 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
class="form-input pl-10 w-full rounded-lg border-secondary-300 dark:border-secondary-700 dark:bg-secondary-800 dark:text-secondary-100 focus:ring-primary-500 focus:border-primary-500"
|
||||
placeholder="--transfers 4 --checkers 8"/>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-secondary-500 dark:text-secondary-400">Optional: Additional rclone flags for fine-tuning the transfer.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,10 +5,11 @@ go 1.24.0
|
||||
require (
|
||||
github.com/a-h/templ v0.3.833
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
golang.org/x/crypto v0.35.0
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
@@ -16,22 +17,25 @@ require (
|
||||
github.com/bytedance/sonic v1.12.9 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.25.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
golang.org/x/arch v0.14.0 // indirect
|
||||
@@ -40,4 +44,8 @@ require (
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.23.1 // indirect
|
||||
)
|
||||
|
||||
@@ -11,12 +11,20 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3 h1:ei3Vq/rpPI/jCJY9mRHJAKg5vU+EhZyWhBAkaAomQuw=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3/go.mod h1:VJ9FIOBAur+NmQ8c4tDVwOuiJcgupTG105FexPFrXzA=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
@@ -32,6 +40,10 @@ github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
@@ -46,8 +58,6 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -57,6 +67,9 @@ github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNH
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -93,8 +106,14 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
||||
+146
-2
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/starfleetcptn/gomft/internal/auth"
|
||||
"gorm.io/driver/sqlite"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -66,6 +66,11 @@ type TransferConfig struct {
|
||||
SourceDomain string `form:"source_domain"`
|
||||
// FTP source fields
|
||||
SourcePassiveMode bool `gorm:"default:true" form:"source_passive_mode"`
|
||||
// OneDrive and Google Drive source fields
|
||||
SourceClientID string `form:"source_client_id"`
|
||||
SourceClientSecret string `form:"source_client_secret" gorm:"-"` // Not stored in DB, only used for form
|
||||
SourceDriveID string `form:"source_drive_id"` // For OneDrive
|
||||
SourceTeamDrive string `form:"source_team_drive"` // For Google Drive
|
||||
// General fields
|
||||
FilePattern string `gorm:"default:'*'" form:"file_pattern"`
|
||||
OutputPattern string `form:"output_pattern"` // Pattern for output filenames with date variables
|
||||
@@ -87,6 +92,11 @@ type TransferConfig struct {
|
||||
DestDomain string `form:"dest_domain"`
|
||||
// FTP destination fields
|
||||
DestPassiveMode bool `gorm:"default:true" form:"dest_passive_mode"`
|
||||
// OneDrive and Google Drive destination fields
|
||||
DestClientID string `form:"dest_client_id"`
|
||||
DestClientSecret string `form:"dest_client_secret" gorm:"-"` // Not stored in DB, only used for form
|
||||
DestDriveID string `form:"dest_drive_id"` // For OneDrive
|
||||
DestTeamDrive string `form:"dest_team_drive"` // For Google Drive
|
||||
// General fields
|
||||
ArchivePath string `form:"archive_path"`
|
||||
ArchiveEnabled bool `gorm:"default:false" form:"archive_enabled"`
|
||||
@@ -136,7 +146,7 @@ func Initialize(dbPath string) (*DB, error) {
|
||||
return nil, fmt.Errorf("failed to create database directory: %v", err)
|
||||
}
|
||||
|
||||
// Open database connection
|
||||
// Open database connection with modernc.org/sqlite driver
|
||||
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to database: %v", err)
|
||||
@@ -426,6 +436,73 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
|
||||
args = append(args, "passive", "true")
|
||||
}
|
||||
|
||||
cmd := exec.Command(rclonePath, args...)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to create source config: %v\nOutput: %s", err, output)
|
||||
}
|
||||
case "webdav":
|
||||
args := []string{
|
||||
"config", "create", sourceName, "webdav",
|
||||
"url", config.SourceHost,
|
||||
"user", config.SourceUser,
|
||||
"pass", config.SourcePassword,
|
||||
"--non-interactive",
|
||||
"--config", configPath,
|
||||
"--log-level", "ERROR",
|
||||
}
|
||||
|
||||
cmd := exec.Command(rclonePath, args...)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to create source config: %v\nOutput: %s", err, output)
|
||||
}
|
||||
case "nextcloud":
|
||||
args := []string{
|
||||
"config", "create", sourceName, "webdav",
|
||||
"url", config.SourceHost,
|
||||
"user", config.SourceUser,
|
||||
"pass", config.SourcePassword,
|
||||
"vendor", "nextcloud",
|
||||
"--non-interactive",
|
||||
"--config", configPath,
|
||||
"--log-level", "ERROR",
|
||||
}
|
||||
|
||||
cmd := exec.Command(rclonePath, args...)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to create source config: %v\nOutput: %s", err, output)
|
||||
}
|
||||
case "onedrive":
|
||||
args := []string{
|
||||
"config", "create", sourceName, "onedrive",
|
||||
"client_id", config.SourceClientID,
|
||||
"client_secret", config.SourceClientSecret,
|
||||
"--non-interactive",
|
||||
"--config", configPath,
|
||||
"--log-level", "ERROR",
|
||||
}
|
||||
|
||||
if config.SourceDriveID != "" {
|
||||
args = append(args, "drive_id", config.SourceDriveID)
|
||||
}
|
||||
|
||||
cmd := exec.Command(rclonePath, args...)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to create source config: %v\nOutput: %s", err, output)
|
||||
}
|
||||
case "google_drive":
|
||||
args := []string{
|
||||
"config", "create", sourceName, "drive",
|
||||
"client_id", config.SourceClientID,
|
||||
"client_secret", config.SourceClientSecret,
|
||||
"--non-interactive",
|
||||
"--config", configPath,
|
||||
"--log-level", "ERROR",
|
||||
}
|
||||
|
||||
if config.SourceTeamDrive != "" {
|
||||
args = append(args, "team_drive", config.SourceTeamDrive)
|
||||
}
|
||||
|
||||
cmd := exec.Command(rclonePath, args...)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to create source config: %v\nOutput: %s", err, output)
|
||||
@@ -547,6 +624,73 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
|
||||
args = append(args, "passive", "true")
|
||||
}
|
||||
|
||||
cmd := exec.Command(rclonePath, args...)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to create destination config: %v\nOutput: %s", err, output)
|
||||
}
|
||||
case "webdav":
|
||||
args := []string{
|
||||
"config", "create", destName, "webdav",
|
||||
"url", config.DestHost,
|
||||
"user", config.DestUser,
|
||||
"pass", config.DestPassword,
|
||||
"--non-interactive",
|
||||
"--config", configPath,
|
||||
"--log-level", "ERROR",
|
||||
}
|
||||
|
||||
cmd := exec.Command(rclonePath, args...)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to create destination config: %v\nOutput: %s", err, output)
|
||||
}
|
||||
case "nextcloud":
|
||||
args := []string{
|
||||
"config", "create", destName, "webdav",
|
||||
"url", config.DestHost,
|
||||
"user", config.DestUser,
|
||||
"pass", config.DestPassword,
|
||||
"vendor", "nextcloud",
|
||||
"--non-interactive",
|
||||
"--config", configPath,
|
||||
"--log-level", "ERROR",
|
||||
}
|
||||
|
||||
cmd := exec.Command(rclonePath, args...)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to create destination config: %v\nOutput: %s", err, output)
|
||||
}
|
||||
case "onedrive":
|
||||
args := []string{
|
||||
"config", "create", destName, "onedrive",
|
||||
"client_id", config.DestClientID,
|
||||
"client_secret", config.DestClientSecret,
|
||||
"--non-interactive",
|
||||
"--config", configPath,
|
||||
"--log-level", "ERROR",
|
||||
}
|
||||
|
||||
if config.DestDriveID != "" {
|
||||
args = append(args, "drive_id", config.DestDriveID)
|
||||
}
|
||||
|
||||
cmd := exec.Command(rclonePath, args...)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to create destination config: %v\nOutput: %s", err, output)
|
||||
}
|
||||
case "google_drive":
|
||||
args := []string{
|
||||
"config", "create", destName, "drive",
|
||||
"client_id", config.DestClientID,
|
||||
"client_secret", config.DestClientSecret,
|
||||
"--non-interactive",
|
||||
"--config", configPath,
|
||||
"--log-level", "ERROR",
|
||||
}
|
||||
|
||||
if config.DestTeamDrive != "" {
|
||||
args = append(args, "team_drive", config.DestTeamDrive)
|
||||
}
|
||||
|
||||
cmd := exec.Command(rclonePath, args...)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to create destination config: %v\nOutput: %s", err, output)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AddCloudStorageFields adds fields for WebDAV, NextCloud, OneDrive, and Google Drive
|
||||
func AddCloudStorageFields() *gormigrate.Migration {
|
||||
return &gormigrate.Migration{
|
||||
ID: "add_cloud_storage_fields",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
// Add source fields
|
||||
if err := tx.Exec("ALTER TABLE transfer_configs ADD COLUMN source_client_id VARCHAR(255)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec("ALTER TABLE transfer_configs ADD COLUMN source_drive_id VARCHAR(255)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec("ALTER TABLE transfer_configs ADD COLUMN source_team_drive VARCHAR(255)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add destination fields
|
||||
if err := tx.Exec("ALTER TABLE transfer_configs ADD COLUMN dest_client_id VARCHAR(255)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec("ALTER TABLE transfer_configs ADD COLUMN dest_drive_id VARCHAR(255)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec("ALTER TABLE transfer_configs ADD COLUMN dest_team_drive VARCHAR(255)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
// Drop source fields
|
||||
if err := tx.Exec("ALTER TABLE transfer_configs DROP COLUMN source_client_id").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec("ALTER TABLE transfer_configs DROP COLUMN source_drive_id").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec("ALTER TABLE transfer_configs DROP COLUMN source_team_drive").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Drop destination fields
|
||||
if err := tx.Exec("ALTER TABLE transfer_configs DROP COLUMN dest_client_id").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec("ALTER TABLE transfer_configs DROP COLUMN dest_drive_id").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec("ALTER TABLE transfer_configs DROP COLUMN dest_team_drive").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,11 @@ import (
|
||||
)
|
||||
|
||||
// InitMigrations initializes the migrations
|
||||
func InitMigrations() *gormigrate.Gormigrate {
|
||||
func InitMigrations(db *gorm.DB) *gormigrate.Gormigrate {
|
||||
migrations := []*gormigrate.Migration{
|
||||
// ... existing migrations
|
||||
AddDeleteAfterTransferColumn(),
|
||||
AddCloudStorageFields(),
|
||||
}
|
||||
|
||||
return gormigrate.New(db, gormigrate.DefaultOptions, migrations)
|
||||
|
||||
@@ -161,8 +161,21 @@ func (s *Scheduler) executeJob(jobID uint) {
|
||||
"--config", configPath,
|
||||
"size",
|
||||
"--include", job.Config.FilePattern,
|
||||
fmt.Sprintf("source_%d:%s", job.Config.ID, job.Config.SourcePath),
|
||||
}
|
||||
|
||||
// Add source path with bucket for S3-compatible storage
|
||||
var sourceSizePath string
|
||||
if job.Config.SourceType == "s3" || job.Config.SourceType == "minio" || job.Config.SourceType == "b2" {
|
||||
sourceSizePath = fmt.Sprintf("source_%d:%s", job.Config.ID, job.Config.SourceBucket)
|
||||
if job.Config.SourcePath != "" && job.Config.SourcePath != "/" {
|
||||
sourceSizePath = fmt.Sprintf("source_%d:%s/%s", job.Config.ID, job.Config.SourceBucket, job.Config.SourcePath)
|
||||
}
|
||||
} else {
|
||||
sourceSizePath = fmt.Sprintf("source_%d:%s", job.Config.ID, job.Config.SourcePath)
|
||||
}
|
||||
|
||||
sizeArgs = append(sizeArgs, sourceSizePath)
|
||||
|
||||
// Get the rclone path from the environment variable or use the default path
|
||||
rclonePath := os.Getenv("RCLONE_PATH")
|
||||
if rclonePath == "" {
|
||||
@@ -202,9 +215,21 @@ func (s *Scheduler) executeJob(jobID uint) {
|
||||
"--config", configPath,
|
||||
"lsf",
|
||||
"--include", job.Config.FilePattern,
|
||||
fmt.Sprintf("source_%d:%s", job.Config.ID, job.Config.SourcePath),
|
||||
}
|
||||
|
||||
// Add source path with bucket for S3-compatible storage
|
||||
var sourceLsPath string
|
||||
if job.Config.SourceType == "s3" || job.Config.SourceType == "minio" || job.Config.SourceType == "b2" {
|
||||
sourceLsPath = fmt.Sprintf("source_%d:%s", job.Config.ID, job.Config.SourceBucket)
|
||||
if job.Config.SourcePath != "" && job.Config.SourcePath != "/" {
|
||||
sourceLsPath = fmt.Sprintf("source_%d:%s/%s", job.Config.ID, job.Config.SourceBucket, job.Config.SourcePath)
|
||||
}
|
||||
} else {
|
||||
sourceLsPath = fmt.Sprintf("source_%d:%s", job.Config.ID, job.Config.SourcePath)
|
||||
}
|
||||
|
||||
listArgs = append(listArgs, sourceLsPath)
|
||||
|
||||
fmt.Printf("Listing files for job %d: rclone %s\n", jobID, strings.Join(listArgs, " "))
|
||||
// Get the rclone path from the environment variable or use the default path
|
||||
rclonePath := os.Getenv("RCLONE_PATH")
|
||||
@@ -247,8 +272,26 @@ func (s *Scheduler) executeJob(jobID uint) {
|
||||
}
|
||||
|
||||
// Source and destination paths
|
||||
sourcePath := fmt.Sprintf("source_%d:%s/%s", job.Config.ID, job.Config.SourcePath, file)
|
||||
destPath := fmt.Sprintf("dest_%d:%s/%s", job.Config.ID, job.Config.DestinationPath, file)
|
||||
var sourcePath, destPath string
|
||||
|
||||
// For S3, MinIO, and B2, include the bucket in the path
|
||||
if job.Config.SourceType == "s3" || job.Config.SourceType == "minio" || job.Config.SourceType == "b2" {
|
||||
sourcePath = fmt.Sprintf("source_%d:%s/%s", job.Config.ID, job.Config.SourceBucket, file)
|
||||
if job.Config.SourcePath != "" && job.Config.SourcePath != "/" {
|
||||
sourcePath = fmt.Sprintf("source_%d:%s/%s/%s", job.Config.ID, job.Config.SourceBucket, job.Config.SourcePath, file)
|
||||
}
|
||||
} else {
|
||||
sourcePath = fmt.Sprintf("source_%d:%s/%s", job.Config.ID, job.Config.SourcePath, file)
|
||||
}
|
||||
|
||||
if job.Config.DestinationType == "s3" || job.Config.DestinationType == "minio" || job.Config.DestinationType == "b2" {
|
||||
destPath = fmt.Sprintf("dest_%d:%s/%s", job.Config.ID, job.Config.DestBucket, file)
|
||||
if job.Config.DestinationPath != "" && job.Config.DestinationPath != "/" {
|
||||
destPath = fmt.Sprintf("dest_%d:%s/%s/%s", job.Config.ID, job.Config.DestBucket, job.Config.DestinationPath, file)
|
||||
}
|
||||
} else {
|
||||
destPath = fmt.Sprintf("dest_%d:%s/%s", job.Config.ID, job.Config.DestinationPath, file)
|
||||
}
|
||||
|
||||
// Add output filename pattern if specified
|
||||
if job.Config.OutputPattern != "" {
|
||||
@@ -299,9 +342,18 @@ func (s *Scheduler) executeJob(jobID uint) {
|
||||
"--config", configPath,
|
||||
"copyto",
|
||||
sourcePath,
|
||||
fmt.Sprintf("source_%d:%s/%s", job.Config.ID, job.Config.ArchivePath, file),
|
||||
}
|
||||
|
||||
// Construct archive path with bucket if needed
|
||||
var archiveDest string
|
||||
if job.Config.SourceType == "s3" || job.Config.SourceType == "minio" || job.Config.SourceType == "b2" {
|
||||
archiveDest = fmt.Sprintf("source_%d:%s/%s/%s", job.Config.ID, job.Config.SourceBucket, job.Config.ArchivePath, file)
|
||||
} else {
|
||||
archiveDest = fmt.Sprintf("source_%d:%s/%s", job.Config.ID, job.Config.ArchivePath, file)
|
||||
}
|
||||
|
||||
archiveArgs = append(archiveArgs, archiveDest)
|
||||
|
||||
fmt.Printf("Executing rclone archive command for job %d, file %s: rclone %s\n",
|
||||
jobID, file, strings.Join(archiveArgs, " "))
|
||||
// Get the rclone path from the environment variable or use the default path
|
||||
@@ -320,8 +372,6 @@ func (s *Scheduler) executeJob(jobID uint) {
|
||||
fmt.Printf("Warning: Error archiving file %s for job %d: %v\n", file, jobID, archiveErr)
|
||||
transferErrors = append(transferErrors,
|
||||
fmt.Sprintf("Archive error for file %s: %v", file, archiveErr))
|
||||
transferErrors = append(transferErrors,
|
||||
fmt.Sprintf("Archive error for file %s: %v", file, archiveErr))
|
||||
}
|
||||
}
|
||||
if job.Config.DeleteAfterTransfer {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -16,6 +19,9 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
//go:embed static
|
||||
var staticFiles embed.FS
|
||||
|
||||
func main() {
|
||||
// Set Gin to release mode
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
@@ -34,10 +40,6 @@ func main() {
|
||||
dirs := []string{
|
||||
cfg.DataDir,
|
||||
cfg.BackupDir,
|
||||
// "templates",
|
||||
"static",
|
||||
"static/css",
|
||||
"static/js",
|
||||
}
|
||||
|
||||
for _, dir := range dirs {
|
||||
@@ -101,9 +103,13 @@ func main() {
|
||||
}))
|
||||
router.Use(gin.Recovery())
|
||||
|
||||
// Serve static files
|
||||
router.Static("/static", "./static")
|
||||
log.Printf("Static file serving configured")
|
||||
// Serve embedded static files
|
||||
staticFS, err := fs.Sub(staticFiles, "static")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create sub-filesystem: %v", err)
|
||||
}
|
||||
router.StaticFS("/static", http.FS(staticFS))
|
||||
log.Printf("Embedded static files configured for serving")
|
||||
|
||||
// Initialize web handlers
|
||||
webHandler, err := web.NewHandler(database, scheduler, cfg.JWTSecret, dbPath, cfg.BackupDir, cfg)
|
||||
|
||||
Reference in New Issue
Block a user