mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-08 15:41:20 +02:00
feat: Add Google Photos support for transfer configurations
- Introduce Google Photos as a source and destination option in the configuration forms. - Implement authentication handling and configuration management for Google Photos. - Update database schema and migrations to include new fields related to Google Photos. - Enhance UI components to support Google Photos-specific settings and forms. - Add tests for Google Photos integration, ensuring proper functionality and authentication flow. - Limit concurrent transfers for Google Photos to ensure compliance with API restrictions.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/starfleetcptn/gomft/components/providers/source"
|
||||
"github.com/starfleetcptn/gomft/components/providers/destination"
|
||||
"github.com/starfleetcptn/gomft/components/providers/common"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ConfigFormData struct {
|
||||
@@ -44,6 +45,10 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
sourceClientSecret := ""
|
||||
sourceDriveId := ""
|
||||
sourceTeamDrive := ""
|
||||
// Google Photos source fields
|
||||
sourceReadOnly := false
|
||||
sourceStartYear, _ := strconv.Atoi(getCurrentYear())
|
||||
sourceIncludeArchived := false
|
||||
|
||||
filePattern := ""
|
||||
outputPattern := "${filename}"
|
||||
@@ -68,6 +73,10 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
destClientSecret := ""
|
||||
destDriveId := ""
|
||||
destTeamDrive := ""
|
||||
// Google Photos destination fields
|
||||
destReadOnly := false
|
||||
destStartYear, _ := strconv.Atoi(getCurrentYear()) // Default to current year int
|
||||
destIncludeArchived := false
|
||||
|
||||
archivePath := ""
|
||||
archiveEnabled := false
|
||||
@@ -103,6 +112,15 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
sourceDriveId = config.SourceDriveID
|
||||
sourceTeamDrive = config.SourceTeamDrive
|
||||
|
||||
// Google Photos source fields
|
||||
if config.SourceReadOnly != nil {
|
||||
sourceReadOnly = *config.SourceReadOnly
|
||||
}
|
||||
sourceStartYear = config.SourceStartYear
|
||||
if config.SourceIncludeArchived != nil {
|
||||
sourceIncludeArchived = *config.SourceIncludeArchived
|
||||
}
|
||||
|
||||
filePattern = config.FilePattern
|
||||
outputPattern = config.OutputPattern
|
||||
|
||||
@@ -129,13 +147,24 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
destDriveId = config.DestDriveID
|
||||
destTeamDrive = config.DestTeamDrive
|
||||
|
||||
// Google Photos destination fields
|
||||
if config.DestReadOnly != nil {
|
||||
destReadOnly = *config.DestReadOnly
|
||||
}
|
||||
destStartYear = config.DestStartYear
|
||||
if config.DestIncludeArchived != nil {
|
||||
destIncludeArchived = *config.DestIncludeArchived
|
||||
}
|
||||
|
||||
archivePath = config.ArchivePath
|
||||
archiveEnabled = config.GetArchiveEnabled()
|
||||
deleteAfterTransfer = config.GetDeleteAfterTransfer()
|
||||
skipProcessedFiles = config.GetSkipProcessedFiles()
|
||||
maxConcurrentTransfers = config.MaxConcurrentTransfers
|
||||
rcloneFlags = config.RcloneFlags
|
||||
if destClientId != "" || destClientSecret != "" {
|
||||
if config.UseBuiltinAuth != nil {
|
||||
useBuiltinAuth = *config.UseBuiltinAuth
|
||||
} else if destClientId != "" || destClientSecret != "" {
|
||||
useBuiltinAuth = false
|
||||
}
|
||||
}
|
||||
@@ -163,6 +192,9 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
sourceClientSecret: '%s',
|
||||
sourceDriveId: '%s',
|
||||
sourceTeamDrive: '%s',
|
||||
sourceReadOnly: %v,
|
||||
sourceStartYear: %d,
|
||||
sourceIncludeArchived: %v,
|
||||
|
||||
filePattern: '%s',
|
||||
outputPattern: '%s',
|
||||
@@ -187,6 +219,10 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
destClientSecret: '%s',
|
||||
destDriveId: '%s',
|
||||
destTeamDrive: '%s',
|
||||
destReadOnly: %v,
|
||||
destStartYear: %d,
|
||||
destIncludeArchived: %v,
|
||||
|
||||
useBuiltinAuth: %v,
|
||||
|
||||
archivePath: '%s',
|
||||
@@ -199,10 +235,12 @@ func getInitialData(config *db.TransferConfig) string {
|
||||
name, sourceType, sourcePath, sourceHost, sourcePort, sourceUser, sourcePassword, sourceKeyFile, sourceAuthType,
|
||||
sourceBucket, sourceRegion, sourceAccessKey, sourceSecretKey, sourceEndpoint, sourceShare, sourceDomain, sourcePassiveMode,
|
||||
sourceClientId, sourceClientSecret, sourceDriveId, sourceTeamDrive,
|
||||
sourceReadOnly, sourceStartYear, sourceIncludeArchived,
|
||||
filePattern, outputPattern,
|
||||
destinationType, destinationPath, destHost, destPort, destUser, destPassword, destKeyFile, destAuthType,
|
||||
destBucket, destRegion, destAccessKey, destSecretKey, destEndpoint, destShare, destDomain, destPassiveMode,
|
||||
destClientId, destClientSecret, destDriveId, destTeamDrive,
|
||||
destReadOnly, destStartYear, destIncludeArchived,
|
||||
useBuiltinAuth,
|
||||
archivePath, archiveEnabled, deleteAfterTransfer, skipProcessedFiles, maxConcurrentTransfers, rcloneFlags)
|
||||
}
|
||||
@@ -287,6 +325,10 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
@source.GoogleDriveSourceForm()
|
||||
</template>
|
||||
|
||||
<template x-if="sourceType === 'gphotos'">
|
||||
@source.GooglePhotosSourceForm()
|
||||
</template>
|
||||
|
||||
<!-- File pattern fields -->
|
||||
@common.FilePatternFields()
|
||||
|
||||
@@ -330,6 +372,9 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) {
|
||||
@destination.GoogleDriveDestinationForm()
|
||||
</template>
|
||||
|
||||
<template x-if="destinationType === 'gphotos'">
|
||||
@destination.GooglePhotosDestinationForm()
|
||||
</template>
|
||||
|
||||
<!-- Archive options -->
|
||||
@common.ArchiveOptions()
|
||||
|
||||
@@ -270,7 +270,7 @@ templ Configs(ctx context.Context, data ConfigsData) {
|
||||
</p>
|
||||
|
||||
<!-- Google Drive Authentication Badge -->
|
||||
if (config.DestinationType == "gdrive" || config.SourceType == "gdrive" || config.DestinationType == "drive") && !config.GetGoogleDriveAuthenticated() {
|
||||
if (config.DestinationType == "gdrive" || config.SourceType == "gdrive" || config.SourceType == "gphotos" || config.DestinationType == "gphotos") && !config.GetGoogleAuthenticated() {
|
||||
<span class="ml-2 px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100">
|
||||
<i class="fas fa-exclamation-triangle mr-1 flex items-center"></i>
|
||||
Authentication Required
|
||||
@@ -278,7 +278,7 @@ templ Configs(ctx context.Context, data ConfigsData) {
|
||||
}
|
||||
|
||||
<!-- Google Drive Authentication Status Indicator -->
|
||||
if (config.DestinationType == "gdrive" || config.SourceType == "gdrive" || config.DestinationType == "drive") && config.GetGoogleDriveAuthenticated() {
|
||||
if (config.DestinationType == "gdrive" || config.SourceType == "gdrive" || config.SourceType == "gphotos" || config.DestinationType == "gphotos") && config.GetGoogleAuthenticated() {
|
||||
<span class="ml-2 px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100">
|
||||
<i class="fas fa-check-circle mr-1 flex items-center"></i>
|
||||
Authenticated
|
||||
@@ -287,7 +287,7 @@ templ Configs(ctx context.Context, data ConfigsData) {
|
||||
</div>
|
||||
<div class="ml-2 flex-shrink-0 flex space-x-2">
|
||||
<!-- Google Drive Authentication Button -->
|
||||
if (config.DestinationType == "gdrive" || config.SourceType == "gdrive") && !config.GetGoogleDriveAuthenticated() {
|
||||
if (config.DestinationType == "gdrive" || config.SourceType == "gdrive" || config.SourceType == "gphotos" || config.DestinationType == "gphotos") && !config.GetGoogleAuthenticated() {
|
||||
<a href={ templ.SafeURL(fmt.Sprintf("/configs/%d/gdrive-auth", config.ID)) } class="btn-warning btn-sm">
|
||||
<i class="fab fa-google-drive mr-1"></i>
|
||||
Authenticate
|
||||
|
||||
@@ -195,6 +195,7 @@ templ SourceSelection() {
|
||||
<option value="nextcloud">NextCloud</option>
|
||||
<option value="webdav">WebDAV</option>
|
||||
<option value="google_drive">Google Drive</option>
|
||||
<option value="gphotos">Google Photos</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -219,6 +220,7 @@ templ DestinationSelection() {
|
||||
<option value="nextcloud">NextCloud</option>
|
||||
<option value="webdav">WebDAV</option>
|
||||
<option value="gdrive">Google Drive</option>
|
||||
<option value="gphotos">Google Photos</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
package destination
|
||||
|
||||
templ GooglePhotosDestinationForm() {
|
||||
<div class="space-y-6" x-init="$watch('useBuiltinAuth', value => {
|
||||
if(value) {
|
||||
destClientId = '';
|
||||
destClientSecret = '';
|
||||
}
|
||||
})">
|
||||
<div class="mb-6">
|
||||
<label for="use_builtin_auth" class="flex items-center cursor-pointer">
|
||||
<div class="relative">
|
||||
<input id="use_builtin_auth" name="use_builtin_auth" type="checkbox"
|
||||
class="sr-only"
|
||||
x-model="useBuiltinAuth"
|
||||
value="true"
|
||||
/>
|
||||
<div class="block bg-gray-200 w-14 h-8 rounded-full"></div>
|
||||
<div class="dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition"
|
||||
:class="useBuiltinAuth ? 'transform translate-x-6 bg-primary-500' : ''"></div>
|
||||
</div>
|
||||
<div class="ml-3 text-gray-700 font-medium">
|
||||
Use rclone's built-in Google authentication (recommended)
|
||||
</div>
|
||||
</label>
|
||||
<p class="mt-1 ml-14 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Simple one-click authentication using rclone's shared credentials
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div x-bind:class="{ 'opacity-50': useBuiltinAuth }">
|
||||
<div>
|
||||
<label for="dest_client_id" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">
|
||||
Google Client ID
|
||||
<span x-show="useBuiltinAuth" class="text-secondary-400 dark:text-secondary-600 text-xs font-normal">(Using rclone default)</span>
|
||||
</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" id="dest_client_id" name="dest_client_id" x-model="destClientId"
|
||||
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"
|
||||
x-bind:disabled="useBuiltinAuth"
|
||||
placeholder="Google Photos OAuth Client ID" />
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Client ID from Google Cloud Console
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="dest_client_secret" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">
|
||||
Google Client Secret
|
||||
<span x-show="useBuiltinAuth" class="text-secondary-400 dark:text-secondary-600 text-xs font-normal">(Using rclone default)</span>
|
||||
</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" id="dest_client_secret" name="dest_client_secret" x-model="destClientSecret"
|
||||
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"
|
||||
x-bind:disabled="useBuiltinAuth"
|
||||
placeholder="Google Photos OAuth Client Secret" />
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Client Secret from Google Cloud Console
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="dest_read_only" class="flex items-center cursor-pointer">
|
||||
<div class="relative">
|
||||
<input id="dest_read_only" name="dest_read_only" type="checkbox"
|
||||
class="sr-only"
|
||||
x-model="destReadOnly"
|
||||
value="true"
|
||||
/>
|
||||
<div class="block bg-gray-200 w-14 h-8 rounded-full"></div>
|
||||
<div class="dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition"
|
||||
:class="destReadOnly ? 'transform translate-x-6 bg-primary-500' : ''"></div>
|
||||
</div>
|
||||
<div class="ml-3 text-gray-700 font-medium">
|
||||
Read-only mode
|
||||
</div>
|
||||
</label>
|
||||
<p class="mt-1 ml-14 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Only request read-only access to your photos
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="dest_start_year" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Start Year (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-calendar text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input type="number" id="dest_start_year" name="dest_start_year" x-model="destStartYear"
|
||||
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="Only include photos after this year (default: 2000)" min="1900" max="2100" />
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Only include photos uploaded after this year
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="dest_include_archived" class="flex items-center cursor-pointer">
|
||||
<div class="relative">
|
||||
<input id="dest_include_archived" name="dest_include_archived" type="checkbox"
|
||||
class="sr-only"
|
||||
x-model="destIncludeArchived"
|
||||
value="true"
|
||||
/>
|
||||
<div class="block bg-gray-200 w-14 h-8 rounded-full"></div>
|
||||
<div class="dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition"
|
||||
:class="destIncludeArchived ? 'transform translate-x-6 bg-primary-500' : ''"></div>
|
||||
</div>
|
||||
<div class="ml-3 text-gray-700 font-medium">
|
||||
Include archived media
|
||||
</div>
|
||||
</label>
|
||||
<p class="mt-1 ml-14 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Include archived photos and videos in media listings
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="destination_path" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Album 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-images text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input type="text" id="destination_path" name="destination_path" x-model="destinationPath"
|
||||
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="Album path (e.g., /album/my-photos)" />
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Path within Google Photos where files will be uploaded
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-amber-50 rounded-lg border border-amber-100">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-exclamation-triangle text-amber-500"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-amber-800">Important: Authentication Required</h3>
|
||||
<div class="mt-2 text-sm text-amber-700">
|
||||
<p><strong>After saving this configuration</strong>, you will need to authenticate with Google Photos.</p>
|
||||
<p class="mt-1">The authentication process will require you to:</p>
|
||||
<ol class="list-decimal list-inside mt-1 space-y-1">
|
||||
<li>Visit a Google authorization URL</li>
|
||||
<li>Sign in to your Google account</li>
|
||||
<li>Grant permission to access your Google Photos</li>
|
||||
<li>Copy the authorization code back to this application</li>
|
||||
</ol>
|
||||
<p class="mt-2 text-xs">
|
||||
This is a one-time process for each configuration. The application will store your authorization token securely.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-blue-50 rounded-lg border border-blue-100">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-info-circle text-blue-400"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-blue-800">Authentication Information</h3>
|
||||
<div class="mt-2 text-sm text-blue-700">
|
||||
<template x-if="useBuiltinAuth">
|
||||
<div>
|
||||
<p>You're using rclone's built-in authentication, which simplifies the setup process:</p>
|
||||
<ul class="list-disc list-inside mt-1 space-y-1">
|
||||
<li>No need to create your own Google API credentials</li>
|
||||
<li>Authentication happens through a browser window</li>
|
||||
<li>You will need to manually copy the authorization code back</li>
|
||||
</ul>
|
||||
<p class="mt-2 text-xs text-amber-600">
|
||||
<i class="fas fa-exclamation-triangle mr-1"></i>
|
||||
Note: The built-in authentication uses shared credentials which have rate limits across all rclone users.
|
||||
If you plan to transfer large amounts of data or run many concurrent transfers, consider creating your own credentials.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="!useBuiltinAuth">
|
||||
<div>
|
||||
<p>To use Google Photos with your own credentials:</p>
|
||||
<ol class="list-decimal list-inside mt-1 space-y-1">
|
||||
<li>Go to the <a href="https://console.cloud.google.com/" target="_blank" class="text-blue-600 underline">Google Cloud Console</a></li>
|
||||
<li>Create a project and enable the Google Photos API</li>
|
||||
<li>Create OAuth 2.0 credentials (Client ID & Secret)</li>
|
||||
<li>Set authorized redirect URI to <code class="bg-blue-100 px-1 py-0.5 rounded">http://localhost:53682/</code></li>
|
||||
</ol>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-yellow-50 rounded-lg border border-yellow-100">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-exclamation-circle text-yellow-500"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-yellow-800">Important Note About Google Photos</h3>
|
||||
<div class="mt-2 text-sm text-yellow-700">
|
||||
<p>All media items uploaded to Google Photos with rclone are stored in full resolution at original quality. These uploads will count towards storage in your Google Account.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package source
|
||||
|
||||
templ GooglePhotosSourceForm() {
|
||||
<div class="space-y-6" x-init="$watch('useBuiltinAuth', value => {
|
||||
if(value) {
|
||||
sourceClientId = '';
|
||||
sourceClientSecret = '';
|
||||
}
|
||||
})">
|
||||
<div class="mb-6">
|
||||
<label for="use_builtin_auth" class="flex items-center cursor-pointer">
|
||||
<div class="relative">
|
||||
<input id="use_builtin_auth" name="use_builtin_auth" type="checkbox"
|
||||
class="sr-only"
|
||||
x-model="useBuiltinAuth"
|
||||
value="true"
|
||||
/>
|
||||
<div class="block bg-gray-200 w-14 h-8 rounded-full"></div>
|
||||
<div class="dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition"
|
||||
:class="useBuiltinAuth ? 'transform translate-x-6 bg-primary-500' : ''"></div>
|
||||
</div>
|
||||
<div class="ml-3 text-gray-700 font-medium">
|
||||
Use rclone's built-in Google authentication (recommended)
|
||||
</div>
|
||||
</label>
|
||||
<p class="mt-1 ml-14 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Simple one-click authentication using rclone's shared credentials
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div x-bind:class="{ 'opacity-50': useBuiltinAuth }">
|
||||
<div>
|
||||
<label for="source_client_id" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">
|
||||
Google Client ID
|
||||
<span x-show="useBuiltinAuth" class="text-secondary-400 dark:text-secondary-600 text-xs font-normal">(Using rclone default)</span>
|
||||
</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" id="source_client_id" name="source_client_id" x-model="sourceClientId"
|
||||
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"
|
||||
x-bind:disabled="useBuiltinAuth"
|
||||
placeholder="Google Photos OAuth Client ID" />
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Client ID from Google Cloud Console
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="source_client_secret" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">
|
||||
Google Client Secret
|
||||
<span x-show="useBuiltinAuth" class="text-secondary-400 dark:text-secondary-600 text-xs font-normal">(Using rclone default)</span>
|
||||
</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" id="source_client_secret" name="source_client_secret" x-model="sourceClientSecret"
|
||||
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"
|
||||
x-bind:disabled="useBuiltinAuth"
|
||||
placeholder="Google Photos OAuth Client Secret" />
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Client Secret from Google Cloud Console
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="source_read_only" class="flex items-center cursor-pointer">
|
||||
<div class="relative">
|
||||
<input id="source_read_only" name="source_read_only" type="checkbox"
|
||||
class="sr-only"
|
||||
x-model="sourceReadOnly"
|
||||
value="true"
|
||||
/>
|
||||
<div class="block bg-gray-200 w-14 h-8 rounded-full"></div>
|
||||
<div class="dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition"
|
||||
:class="sourceReadOnly ? 'transform translate-x-6 bg-primary-500' : ''"></div>
|
||||
</div>
|
||||
<div class="ml-3 text-gray-700 font-medium">
|
||||
Read-only mode
|
||||
</div>
|
||||
</label>
|
||||
<p class="mt-1 ml-14 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Only request read-only access to your photos
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="source_start_year" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Start Year (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-calendar text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input type="number" id="source_start_year" name="source_start_year" x-model="sourceStartYear"
|
||||
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="Only include photos after this year (default: 2000)" min="1900" max="2100" />
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Only include photos uploaded after this year
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="source_include_archived" class="flex items-center cursor-pointer">
|
||||
<div class="relative">
|
||||
<input id="source_include_archived" name="source_include_archived" type="checkbox"
|
||||
class="sr-only"
|
||||
x-model="sourceIncludeArchived"
|
||||
value="true"
|
||||
/>
|
||||
<div class="block bg-gray-200 w-14 h-8 rounded-full"></div>
|
||||
<div class="dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition"
|
||||
:class="sourceIncludeArchived ? 'transform translate-x-6 bg-primary-500' : ''"></div>
|
||||
</div>
|
||||
<div class="ml-3 text-gray-700 font-medium">
|
||||
Include archived media
|
||||
</div>
|
||||
</label>
|
||||
<p class="mt-1 ml-14 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Include archived photos and videos in media listings
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="source_path" class="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">Album 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-images text-secondary-400 dark:text-secondary-600"></i>
|
||||
</div>
|
||||
<input type="text" id="source_path" name="source_path" x-model="sourcePath"
|
||||
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="Album path (e.g., /album/my-photos)" />
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
|
||||
Path within Google Photos to download files from
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-amber-50 rounded-lg border border-amber-100">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-exclamation-triangle text-amber-500"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-amber-800">Important: Authentication Required</h3>
|
||||
<div class="mt-2 text-sm text-amber-700">
|
||||
<p><strong>After saving this configuration</strong>, you will need to authenticate with Google Photos.</p>
|
||||
<p class="mt-1">The authentication process will require you to:</p>
|
||||
<ol class="list-decimal list-inside mt-1 space-y-1">
|
||||
<li>Visit a Google authorization URL</li>
|
||||
<li>Sign in to your Google account</li>
|
||||
<li>Grant permission to access your Google Photos</li>
|
||||
<li>Copy the authorization code back to this application</li>
|
||||
</ol>
|
||||
<p class="mt-2 text-xs">
|
||||
This is a one-time process for each configuration. The application will store your authorization token securely.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-blue-50 rounded-lg border border-blue-100">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-info-circle text-blue-400"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-blue-800">Authentication Information</h3>
|
||||
<div class="mt-2 text-sm text-blue-700">
|
||||
<template x-if="useBuiltinAuth">
|
||||
<div>
|
||||
<p>You're using rclone's built-in authentication, which simplifies the setup process:</p>
|
||||
<ul class="list-disc list-inside mt-1 space-y-1">
|
||||
<li>No need to create your own Google API credentials</li>
|
||||
<li>Authentication happens through a browser window</li>
|
||||
<li>You will need to manually copy the authorization code back</li>
|
||||
</ul>
|
||||
<p class="mt-2 text-xs text-amber-600">
|
||||
<i class="fas fa-exclamation-triangle mr-1"></i>
|
||||
Note: The built-in authentication uses shared credentials which have rate limits across all rclone users.
|
||||
If you plan to transfer large amounts of data or run many concurrent transfers, consider creating your own credentials.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="!useBuiltinAuth">
|
||||
<div>
|
||||
<p>To use Google Photos with your own credentials:</p>
|
||||
<ol class="list-decimal list-inside mt-1 space-y-1">
|
||||
<li>Go to the <a href="https://console.cloud.google.com/" target="_blank" class="text-blue-600 underline">Google Cloud Console</a></li>
|
||||
<li>Create a project and enable the Google Photos API</li>
|
||||
<li>Create OAuth 2.0 credentials (Client ID & Secret)</li>
|
||||
<li>Set authorized redirect URI to <code class="bg-blue-100 px-1 py-0.5 rounded">http://localhost:53682/</code></li>
|
||||
</ol>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-yellow-50 rounded-lg border border-yellow-100">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<i class="fas fa-exclamation-circle text-yellow-500"></i>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-yellow-800">Important Note About Google Photos</h3>
|
||||
<div class="mt-2 text-sm text-yellow-700">
|
||||
<p>When downloading from Google Photos, be aware that some original metadata may not be preserved. Google Photos processes and may compress some images upon upload.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
Reference in New Issue
Block a user