mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-20 13:30:51 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bee14b7de0 | ||
|
|
6c96cdbec1 | ||
|
|
339299e08e | ||
|
|
1b1d043d50 | ||
|
|
c3a22333f7 | ||
|
|
ea057f14dc | ||
|
|
0da968fd5a |
+2
-1
@@ -44,6 +44,7 @@ Thumbs.db
|
|||||||
|
|
||||||
# Ignore all Go files in the components directory
|
# Ignore all Go files in the components directory
|
||||||
components/*.go
|
components/*.go
|
||||||
|
!components/components.go
|
||||||
|
|
||||||
# Ignore the data directory
|
# Ignore the data directory
|
||||||
data/
|
data/
|
||||||
@@ -60,4 +61,4 @@ destination/
|
|||||||
archive/
|
archive/
|
||||||
|
|
||||||
# Ignore binaries
|
# Ignore binaries
|
||||||
gomft
|
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 := ""
|
sourceDomain := ""
|
||||||
// FTP fields
|
// FTP fields
|
||||||
sourcePassiveMode := true
|
sourcePassiveMode := true
|
||||||
|
// OneDrive and Google Drive fields
|
||||||
|
sourceClientId := ""
|
||||||
|
sourceClientSecret := ""
|
||||||
|
sourceDriveId := ""
|
||||||
|
sourceTeamDrive := ""
|
||||||
filePattern := "*"
|
filePattern := "*"
|
||||||
outputPattern := ""
|
outputPattern := ""
|
||||||
destinationType := "local"
|
destinationType := "local"
|
||||||
@@ -58,6 +63,11 @@ func getInitialData(config *db.TransferConfig) string {
|
|||||||
destDomain := ""
|
destDomain := ""
|
||||||
// FTP fields
|
// FTP fields
|
||||||
destPassiveMode := true
|
destPassiveMode := true
|
||||||
|
// OneDrive and Google Drive fields
|
||||||
|
destClientId := ""
|
||||||
|
destClientSecret := ""
|
||||||
|
destDriveId := ""
|
||||||
|
destTeamDrive := ""
|
||||||
archivePath := ""
|
archivePath := ""
|
||||||
archiveEnabled := false
|
archiveEnabled := false
|
||||||
deleteAfterTransfer := false
|
deleteAfterTransfer := false
|
||||||
@@ -83,6 +93,11 @@ func getInitialData(config *db.TransferConfig) string {
|
|||||||
sourceDomain = config.SourceDomain
|
sourceDomain = config.SourceDomain
|
||||||
// FTP fields
|
// FTP fields
|
||||||
sourcePassiveMode = config.SourcePassiveMode
|
sourcePassiveMode = config.SourcePassiveMode
|
||||||
|
// OneDrive and Google Drive fields
|
||||||
|
sourceClientId = config.SourceClientID
|
||||||
|
sourceClientSecret = config.SourceClientSecret
|
||||||
|
sourceDriveId = config.SourceDriveID
|
||||||
|
sourceTeamDrive = config.SourceTeamDrive
|
||||||
filePattern = config.FilePattern
|
filePattern = config.FilePattern
|
||||||
outputPattern = config.OutputPattern
|
outputPattern = config.OutputPattern
|
||||||
destinationType = config.DestinationType
|
destinationType = config.DestinationType
|
||||||
@@ -103,6 +118,11 @@ func getInitialData(config *db.TransferConfig) string {
|
|||||||
destDomain = config.DestDomain
|
destDomain = config.DestDomain
|
||||||
// FTP fields
|
// FTP fields
|
||||||
destPassiveMode = config.DestPassiveMode
|
destPassiveMode = config.DestPassiveMode
|
||||||
|
// OneDrive and Google Drive fields
|
||||||
|
destClientId = config.DestClientID
|
||||||
|
destClientSecret = config.DestClientSecret
|
||||||
|
destDriveId = config.DestDriveID
|
||||||
|
destTeamDrive = config.DestTeamDrive
|
||||||
archivePath = config.ArchivePath
|
archivePath = config.ArchivePath
|
||||||
archiveEnabled = config.ArchiveEnabled
|
archiveEnabled = config.ArchiveEnabled
|
||||||
deleteAfterTransfer = config.DeleteAfterTransfer
|
deleteAfterTransfer = config.DeleteAfterTransfer
|
||||||
@@ -126,6 +146,10 @@ func getInitialData(config *db.TransferConfig) string {
|
|||||||
sourceShare: '%s',
|
sourceShare: '%s',
|
||||||
sourceDomain: '%s',
|
sourceDomain: '%s',
|
||||||
sourcePassiveMode: %v,
|
sourcePassiveMode: %v,
|
||||||
|
sourceClientId: '%s',
|
||||||
|
sourceClientSecret: '%s',
|
||||||
|
sourceDriveId: '%s',
|
||||||
|
sourceTeamDrive: '%s',
|
||||||
filePattern: '%s',
|
filePattern: '%s',
|
||||||
outputPattern: '%s',
|
outputPattern: '%s',
|
||||||
destinationType: '%s',
|
destinationType: '%s',
|
||||||
@@ -146,12 +170,18 @@ func getInitialData(config *db.TransferConfig) string {
|
|||||||
destDomain: '%s',
|
destDomain: '%s',
|
||||||
// FTP fields
|
// FTP fields
|
||||||
destPassiveMode: %v,
|
destPassiveMode: %v,
|
||||||
|
// OneDrive and Google Drive fields
|
||||||
|
destClientId: '%s',
|
||||||
|
destClientSecret: '%s',
|
||||||
|
destDriveId: '%s',
|
||||||
|
destTeamDrive: '%s',
|
||||||
// Existing fields
|
// Existing fields
|
||||||
archivePath: '%s',
|
archivePath: '%s',
|
||||||
archiveEnabled: %v,
|
archiveEnabled: %v,
|
||||||
deleteAfterTransfer: %v,
|
deleteAfterTransfer: %v,
|
||||||
rcloneFlags: '%s',
|
rcloneFlags: '%s',
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|
||||||
validate() {
|
validate() {
|
||||||
if (this.sourceType === 'sftp') {
|
if (this.sourceType === 'sftp') {
|
||||||
if (!this.sourceHost || !this.sourceUser || (!this.sourcePassword && !this.sourceKeyFile)) {
|
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) {
|
if (!this.sourceHost || !this.sourceUser || !this.sourcePassword) {
|
||||||
return false;
|
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') {
|
if (this.destinationType === 'sftp') {
|
||||||
@@ -203,15 +249,33 @@ func getInitialData(config *db.TransferConfig) string {
|
|||||||
if (!this.destHost || !this.destUser || !this.destPassword) {
|
if (!this.destHost || !this.destUser || !this.destPassword) {
|
||||||
return false;
|
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);
|
return this.name && this.sourcePath && this.filePattern && this.destinationPath && (!this.archiveEnabled || this.archivePath);
|
||||||
}
|
}
|
||||||
}`, name, sourceType, sourcePath, sourceHost, sourcePort, sourceUser, sourcePassword, sourceKeyFile,
|
}`, name, sourceType, sourcePath, sourceHost, sourcePort, sourceUser, sourcePassword, sourceKeyFile,
|
||||||
sourceBucket, sourceRegion, sourceAccessKey, sourceSecretKey, sourceEndpoint,
|
sourceBucket, sourceRegion, sourceAccessKey, sourceSecretKey, sourceEndpoint,
|
||||||
sourceShare, sourceDomain, sourcePassiveMode,
|
sourceShare, sourceDomain, sourcePassiveMode,
|
||||||
|
sourceClientId, sourceClientSecret, sourceDriveId, sourceTeamDrive,
|
||||||
filePattern, outputPattern, destinationType, destinationPath, destHost, destPort, destUser, destPassword, destKeyFile,
|
filePattern, outputPattern, destinationType, destinationPath, destHost, destPort, destUser, destPassword, destKeyFile,
|
||||||
destBucket, destRegion, destAccessKey, destSecretKey, destEndpoint,
|
destBucket, destRegion, destAccessKey, destSecretKey, destEndpoint,
|
||||||
destShare, destDomain, destPassiveMode,
|
destShare, destDomain, destPassiveMode,
|
||||||
|
destClientId, destClientSecret, destDriveId, destTeamDrive,
|
||||||
archivePath, archiveEnabled, deleteAfterTransfer, rcloneFlags)
|
archivePath, archiveEnabled, deleteAfterTransfer, rcloneFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,6 +348,10 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
|||||||
<option value="b2">Backblaze B2</option>
|
<option value="b2">Backblaze B2</option>
|
||||||
<option value="smb">SMB</option>
|
<option value="smb">SMB</option>
|
||||||
<option value="ftp">FTP</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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -728,6 +796,268 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</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">
|
<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>
|
<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">
|
<div class="relative">
|
||||||
@@ -814,6 +1144,10 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
|||||||
<option value="b2">Backblaze B2</option>
|
<option value="b2">Backblaze B2</option>
|
||||||
<option value="smb">SMB</option>
|
<option value="smb">SMB</option>
|
||||||
<option value="ftp">FTP</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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1258,15 +1592,277 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</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">
|
<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>
|
<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="relative">
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
<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>
|
<i class="fas fa-folder-open text-secondary-400 dark:text-secondary-600"></i>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="destination_path"
|
name="destination_path"
|
||||||
id="destination_path"
|
id="destination_path"
|
||||||
x-model="destinationPath"
|
x-model="destinationPath"
|
||||||
required
|
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"
|
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"/>
|
placeholder="/path/to/archive"/>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-sm text-secondary-500 dark:text-secondary-400">Location to move files after successful transfer</p>
|
|
||||||
</div>
|
</div>
|
||||||
</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"
|
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"/>
|
placeholder="--transfers 4 --checkers 8"/>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ type TransferConfig struct {
|
|||||||
SourceDomain string `form:"source_domain"`
|
SourceDomain string `form:"source_domain"`
|
||||||
// FTP source fields
|
// FTP source fields
|
||||||
SourcePassiveMode bool `gorm:"default:true" form:"source_passive_mode"`
|
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
|
// General fields
|
||||||
FilePattern string `gorm:"default:'*'" form:"file_pattern"`
|
FilePattern string `gorm:"default:'*'" form:"file_pattern"`
|
||||||
OutputPattern string `form:"output_pattern"` // Pattern for output filenames with date variables
|
OutputPattern string `form:"output_pattern"` // Pattern for output filenames with date variables
|
||||||
@@ -87,6 +92,11 @@ type TransferConfig struct {
|
|||||||
DestDomain string `form:"dest_domain"`
|
DestDomain string `form:"dest_domain"`
|
||||||
// FTP destination fields
|
// FTP destination fields
|
||||||
DestPassiveMode bool `gorm:"default:true" form:"dest_passive_mode"`
|
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
|
// General fields
|
||||||
ArchivePath string `form:"archive_path"`
|
ArchivePath string `form:"archive_path"`
|
||||||
ArchiveEnabled bool `gorm:"default:false" form:"archive_enabled"`
|
ArchiveEnabled bool `gorm:"default:false" form:"archive_enabled"`
|
||||||
@@ -426,6 +436,73 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
|
|||||||
args = append(args, "passive", "true")
|
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...)
|
cmd := exec.Command(rclonePath, args...)
|
||||||
if output, err := cmd.CombinedOutput(); err != nil {
|
if output, err := cmd.CombinedOutput(); err != nil {
|
||||||
return fmt.Errorf("failed to create source config: %v\nOutput: %s", err, output)
|
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")
|
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...)
|
cmd := exec.Command(rclonePath, args...)
|
||||||
if output, err := cmd.CombinedOutput(); err != nil {
|
if output, err := cmd.CombinedOutput(); err != nil {
|
||||||
return fmt.Errorf("failed to create destination config: %v\nOutput: %s", err, output)
|
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
|
// InitMigrations initializes the migrations
|
||||||
func InitMigrations() *gormigrate.Gormigrate {
|
func InitMigrations(db *gorm.DB) *gormigrate.Gormigrate {
|
||||||
migrations := []*gormigrate.Migration{
|
migrations := []*gormigrate.Migration{
|
||||||
// ... existing migrations
|
// ... existing migrations
|
||||||
AddDeleteAfterTransferColumn(),
|
AddDeleteAfterTransferColumn(),
|
||||||
|
AddCloudStorageFields(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return gormigrate.New(db, gormigrate.DefaultOptions, migrations)
|
return gormigrate.New(db, gormigrate.DefaultOptions, migrations)
|
||||||
|
|||||||
@@ -161,8 +161,21 @@ func (s *Scheduler) executeJob(jobID uint) {
|
|||||||
"--config", configPath,
|
"--config", configPath,
|
||||||
"size",
|
"size",
|
||||||
"--include", job.Config.FilePattern,
|
"--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
|
// Get the rclone path from the environment variable or use the default path
|
||||||
rclonePath := os.Getenv("RCLONE_PATH")
|
rclonePath := os.Getenv("RCLONE_PATH")
|
||||||
if rclonePath == "" {
|
if rclonePath == "" {
|
||||||
@@ -202,9 +215,21 @@ func (s *Scheduler) executeJob(jobID uint) {
|
|||||||
"--config", configPath,
|
"--config", configPath,
|
||||||
"lsf",
|
"lsf",
|
||||||
"--include", job.Config.FilePattern,
|
"--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, " "))
|
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
|
// Get the rclone path from the environment variable or use the default path
|
||||||
rclonePath := os.Getenv("RCLONE_PATH")
|
rclonePath := os.Getenv("RCLONE_PATH")
|
||||||
@@ -247,8 +272,26 @@ func (s *Scheduler) executeJob(jobID uint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Source and destination paths
|
// Source and destination paths
|
||||||
sourcePath := fmt.Sprintf("source_%d:%s/%s", job.Config.ID, job.Config.SourcePath, file)
|
var sourcePath, destPath string
|
||||||
destPath := fmt.Sprintf("dest_%d:%s/%s", job.Config.ID, job.Config.DestinationPath, file)
|
|
||||||
|
// 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
|
// Add output filename pattern if specified
|
||||||
if job.Config.OutputPattern != "" {
|
if job.Config.OutputPattern != "" {
|
||||||
@@ -299,9 +342,18 @@ func (s *Scheduler) executeJob(jobID uint) {
|
|||||||
"--config", configPath,
|
"--config", configPath,
|
||||||
"copyto",
|
"copyto",
|
||||||
sourcePath,
|
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",
|
fmt.Printf("Executing rclone archive command for job %d, file %s: rclone %s\n",
|
||||||
jobID, file, strings.Join(archiveArgs, " "))
|
jobID, file, strings.Join(archiveArgs, " "))
|
||||||
// Get the rclone path from the environment variable or use the default path
|
// 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)
|
fmt.Printf("Warning: Error archiving file %s for job %d: %v\n", file, jobID, archiveErr)
|
||||||
transferErrors = append(transferErrors,
|
transferErrors = append(transferErrors,
|
||||||
fmt.Sprintf("Archive error for file %s: %v", file, archiveErr))
|
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 {
|
if job.Config.DeleteAfterTransfer {
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
@@ -16,6 +19,9 @@ import (
|
|||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed static
|
||||||
|
var staticFiles embed.FS
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Set Gin to release mode
|
// Set Gin to release mode
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
@@ -34,10 +40,6 @@ func main() {
|
|||||||
dirs := []string{
|
dirs := []string{
|
||||||
cfg.DataDir,
|
cfg.DataDir,
|
||||||
cfg.BackupDir,
|
cfg.BackupDir,
|
||||||
// "templates",
|
|
||||||
"static",
|
|
||||||
"static/css",
|
|
||||||
"static/js",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dir := range dirs {
|
for _, dir := range dirs {
|
||||||
@@ -101,9 +103,13 @@ func main() {
|
|||||||
}))
|
}))
|
||||||
router.Use(gin.Recovery())
|
router.Use(gin.Recovery())
|
||||||
|
|
||||||
// Serve static files
|
// Serve embedded static files
|
||||||
router.Static("/static", "./static")
|
staticFS, err := fs.Sub(staticFiles, "static")
|
||||||
log.Printf("Static file serving configured")
|
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
|
// Initialize web handlers
|
||||||
webHandler, err := web.NewHandler(database, scheduler, cfg.JWTSecret, dbPath, cfg.BackupDir, cfg)
|
webHandler, err := web.NewHandler(database, scheduler, cfg.JWTSecret, dbPath, cfg.BackupDir, cfg)
|
||||||
|
|||||||
Reference in New Issue
Block a user