- Fix arr setup getting thr wrong crendentials - Add file link invalidator - Other minor bug fixes
959 lines
61 KiB
HTML
959 lines
61 KiB
HTML
{{ define "config" }}
|
|
<div class="space-y-6">
|
|
{{ if .NeedSetup }}
|
|
<div role="alert" class="alert alert-warning">
|
|
<i class="bi bi-exclamation-triangle text-xl"></i>
|
|
<div>
|
|
<h3 class="font-bold">Configuration Required</h3>
|
|
<div class="text-sm">Your configuration is incomplete. Please complete the setup below.</div>
|
|
</div>
|
|
</div>
|
|
{{ end }}
|
|
|
|
<form id="configForm" class="space-y-6">
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<div class="border-b border-base-300 mb-8">
|
|
<nav class="flex space-x-8" aria-label="Configuration Tabs">
|
|
<button type="button" class="tab-button active flex items-center gap-2 py-3 px-1 border-b-2 border-primary text-primary font-medium text-sm" data-tab="general">
|
|
<i class="bi bi-gear text-lg"></i>
|
|
<span class="hidden sm:inline">General</span>
|
|
</button>
|
|
<button type="button" class="tab-button flex items-center gap-2 py-3 px-1 border-b-2 border-transparent text-base-content/70 hover:text-base-content hover:border-base-300 font-medium text-sm transition-colors" data-tab="debrid">
|
|
<i class="bi bi-cloud text-lg"></i>
|
|
<span class="hidden sm:inline">Debrid</span>
|
|
</button>
|
|
<button type="button" class="tab-button flex items-center gap-2 py-3 px-1 border-b-2 border-transparent text-base-content/70 hover:text-base-content hover:border-base-300 font-medium text-sm transition-colors" data-tab="qbittorrent">
|
|
<i class="bi bi-download text-lg"></i>
|
|
<span class="hidden sm:inline">QBittorrent</span>
|
|
</button>
|
|
<button type="button" class="tab-button flex items-center gap-2 py-3 px-1 border-b-2 border-transparent text-base-content/70 hover:text-base-content hover:border-base-300 font-medium text-sm transition-colors" data-tab="arrs">
|
|
<i class="bi bi-collection text-lg"></i>
|
|
<span class="hidden sm:inline">*Arrs</span>
|
|
</button>
|
|
<button type="button" class="tab-button flex items-center gap-2 py-3 px-1 border-b-2 border-transparent text-base-content/70 hover:text-base-content hover:border-base-300 font-medium text-sm transition-colors" data-tab="repair">
|
|
<i class="bi bi-wrench text-lg"></i>
|
|
<span class="hidden sm:inline">Repair</span>
|
|
</button>
|
|
<button type="button" class="tab-button flex items-center gap-2 py-3 px-1 border-b-2 border-transparent text-base-content/70 hover:text-base-content hover:border-base-300 font-medium text-sm transition-colors" data-tab="rclone">
|
|
<i class="bi bi-hdd-stack text-lg"></i>
|
|
<span class="hidden sm:inline">Rclone</span>
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
|
|
<div class="sticky top-20 z-30 flex justify-end mb-6">
|
|
<button type="submit" class="btn btn-success btn-lg shadow-lg">
|
|
<i class="bi bi-save mr-2"></i>Save Configuration
|
|
</button>
|
|
</div>
|
|
|
|
<div class="tab-content-container">
|
|
|
|
<div class="tab-content" data-tab-content="general">
|
|
<div class="space-y-6">
|
|
<h2 class="text-2xl font-bold flex items-center mb-6">
|
|
<i class="bi bi-gear mr-3 text-primary"></i>General Settings
|
|
</h2>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<div class="form-control">
|
|
<label class="label" for="log-level">
|
|
<span class="label-text font-medium">Log Level</span>
|
|
</label>
|
|
<select class="select select-bordered" name="log_level" id="log-level">
|
|
<option value="info">Info</option>
|
|
<option value="debug">Debug</option>
|
|
<option value="warn">Warning</option>
|
|
<option value="error">Error</option>
|
|
<option value="trace">Trace</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text font-medium">Magnet Link Handler</span>
|
|
</label>
|
|
<button type="button" class="btn btn-primary" onclick="registerMagnetLinkHandler();" id="registerMagnetLink">
|
|
<i class="bi bi-magnet mr-2"></i>Register Magnet Handler
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
|
<div class="form-control">
|
|
<label class="label" for="urlBase">
|
|
<span class="label-text font-medium">URL Base</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" id="urlBase" name="url_base" placeholder="/">
|
|
<div class="label">
|
|
<span class="label-text-alt">URL base for the application</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="bindAddress">
|
|
<span class="label-text font-medium">Bind Address</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" id="bindAddress" name="bind_address" placeholder="0.0.0.0">
|
|
<div class="label">
|
|
<span class="label-text-alt">Bind address (default: all interfaces)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="port">
|
|
<span class="label-text font-medium">Port</span>
|
|
</label>
|
|
<input type="number" class="input input-bordered" id="port" name="port" placeholder="8282">
|
|
<div class="label">
|
|
<span class="label-text-alt">Application port</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<div class="form-control">
|
|
<label class="label" for="discordWebhookUrl">
|
|
<span class="label-text font-medium">Discord Webhook URL</span>
|
|
</label>
|
|
<textarea class="textarea textarea-bordered" id="discordWebhookUrl" name="discord_webhook_url" placeholder="https://discord.com/api/webhooks/..."></textarea>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="allowedExtensions">
|
|
<span class="label-text font-medium">Allowed File Extensions</span>
|
|
</label>
|
|
<textarea class="textarea textarea-bordered" id="allowedExtensions" name="allowed_file_types" placeholder="mkv, mp4, avi, mov"></textarea>
|
|
<div class="label">
|
|
<span class="label-text-alt">Comma-separated list of allowed file extensions</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<div class="form-control">
|
|
<label class="label" for="minFileSize">
|
|
<span class="label-text font-medium">Minimum File Size</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" id="minFileSize" name="min_file_size" placeholder="10MB">
|
|
<div class="label">
|
|
<span class="label-text-alt">Minimum file size to download</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="maxFileSize">
|
|
<span class="label-text font-medium">Maximum File Size</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" id="maxFileSize" name="max_file_size" placeholder="50GB">
|
|
<div class="label">
|
|
<span class="label-text-alt">Maximum file size to download</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="removeStalledAfter">
|
|
<span class="label-text font-medium">Remove Stalled After</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" id="removeStalledAfter" name="remove_stalled_after" placeholder="1h">
|
|
<div class="label">
|
|
<span class="label-text-alt">Duration before removing stalled torrents</span>
|
|
</div>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label" for="callbackUrl">
|
|
<span class="label-text font-medium">Callback URL</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" id="callbackUrl" name="callback_url" placeholder="http://example.com/callback">
|
|
<div class="label">
|
|
<span class="label-text-alt">Optional callback URL for download status updates</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Authentication Settings Section -->
|
|
<div class="divider">
|
|
<span class="text-lg font-semibold">Authentication Settings</span>
|
|
</div>
|
|
|
|
<div class="card bg-base-200">
|
|
<div class="card-body">
|
|
<div class="space-y-6">
|
|
<div class="flex justify-between items-start">
|
|
<div class="flex-1">
|
|
<h3 class="text-lg font-semibold mb-2">Authentication Settings</h3>
|
|
<p class="text-sm text-base-content/70">Configure username/password authentication and API token for programmatic access.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Username/Password Section -->
|
|
<div class="space-y-4">
|
|
<h4 class="font-semibold text-base">Web Authentication</h4>
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text font-medium">Username</span>
|
|
</label>
|
|
<input type="text"
|
|
id="auth-username"
|
|
name="auth_username"
|
|
class="input input-bordered"
|
|
placeholder="Enter username (leave empty to disable auth)">
|
|
<div class="label">
|
|
<span class="label-text-alt">Leave empty to disable authentication</span>
|
|
</div>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text font-medium">Password</span>
|
|
</label>
|
|
<div class="password-toggle-container">
|
|
<input type="password"
|
|
id="auth-password"
|
|
name="auth_password"
|
|
class="input input-bordered input-has-toggle"
|
|
placeholder="Enter password">
|
|
<button type="button" class="password-toggle-btn">
|
|
<i class="bi bi-eye"></i>
|
|
</button>
|
|
</div>
|
|
<div class="label">
|
|
<span class="label-text-alt">Leave empty to disable authentication</span>
|
|
</div>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text font-medium">Confirm Password</span>
|
|
</label>
|
|
<div class="password-toggle-container">
|
|
<input type="password"
|
|
id="auth-password-confirm"
|
|
name="auth_password_confirm"
|
|
class="input input-bordered input-has-toggle"
|
|
placeholder="Confirm password">
|
|
<button type="button" class="password-toggle-btn">
|
|
<i class="bi bi-eye"></i>
|
|
</button>
|
|
</div>
|
|
<div class="label">
|
|
<span class="label-text-alt" id="password-match-indicator"></span>
|
|
</div>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text font-medium">Current Token</span>
|
|
</label>
|
|
<div class="join">
|
|
<input type="text"
|
|
id="api-token-display"
|
|
class="input input-bordered join-item flex-1 font-mono"
|
|
placeholder="No token generated"
|
|
readonly>
|
|
<button type="button"
|
|
id="copy-token-btn"
|
|
class="btn btn-outline join-item"
|
|
onclick="copyAPIToken();">
|
|
<i class="bi bi-copy"></i>
|
|
</button>
|
|
<button type="button"
|
|
id="refresh-token-btn"
|
|
class="btn btn-outline btn-secondary join-item"
|
|
onclick="refreshAPIToken();">
|
|
<i class="bi bi-arrow-clockwise"></i>
|
|
</button>
|
|
</div>
|
|
<div class="label">
|
|
<span class="label-text-alt">Click refresh to generate or update your API token</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-end items-center mt-5">
|
|
<button type="button"
|
|
id="update-auth-btn"
|
|
class="btn btn-primary"
|
|
onclick="updateAuthSettings();">
|
|
<i class="bi bi-shield-check mr-2"></i>Update Authentication
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="space-y-4">
|
|
<p class="text-sm text-base-content/70">Use this token for API authentication instead of session cookies. Perfect for automation and scripts.</p>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-content hidden" data-tab-content="debrid">
|
|
<div class="space-y-6">
|
|
<div class="flex justify-between items-center">
|
|
<h2 class="text-2xl font-bold flex items-center">
|
|
<i class="bi bi-cloud mr-3 text-secondary"></i>Debrid Services
|
|
</h2>
|
|
<button type="button" id="addDebridBtn" class="btn btn-secondary">
|
|
<i class="bi bi-plus mr-2"></i>Add Debrid Service
|
|
</button>
|
|
</div>
|
|
|
|
<div id="debridConfigs" class="space-y-4">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-content hidden" data-tab-content="qbittorrent">
|
|
<div class="space-y-6">
|
|
<h2 class="text-2xl font-bold flex items-center mb-6">
|
|
<i class="bi bi-download mr-3 text-accent"></i>QBittorrent Settings
|
|
</h2>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<div class="form-control">
|
|
<label class="label" for="qbit.download_folder">
|
|
<span class="label-text font-medium">Download Folder</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="qbit.download_folder" id="qbit.download_folder">
|
|
<div class="label">
|
|
<span class="label-text-alt">Folder where downloaded files will be stored</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="qbit.refresh_interval">
|
|
<span class="label-text font-medium">Refresh Interval (seconds)</span>
|
|
</label>
|
|
<input type="number" class="input input-bordered" name="qbit.refresh_interval" id="qbit.refresh_interval" min="1">
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="qbit.max_downloads">
|
|
<span class="label-text font-medium">Maximum Downloads</span>
|
|
</label>
|
|
<input type="number" class="input input-bordered" name="qbit.max_downloads" id="qbit.max_downloads" min="0">
|
|
<div class="label">
|
|
<span class="label-text-alt">Maximum simultaneous downloads (0 = unlimited)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox" name="qbit.skip_pre_cache" id="qbit.skip_pre_cache">
|
|
<div>
|
|
<span class="label-text font-medium">Skip Pre-Cache</span>
|
|
<div class="label-text-alt">Disable pre-caching to speed up imports</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-content hidden" data-tab-content="arrs">
|
|
<div class="space-y-6">
|
|
<div class="flex justify-between items-center">
|
|
<h2 class="text-2xl font-bold flex items-center">
|
|
<i class="bi bi-collection mr-3 text-warning"></i>Arr Applications
|
|
</h2>
|
|
<button type="button" id="addArrBtn" class="btn btn-warning">
|
|
<i class="bi bi-plus mr-2"></i>Add Arr Service
|
|
</button>
|
|
</div>
|
|
|
|
<div id="arrConfigs" class="space-y-4">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-content hidden" data-tab-content="repair">
|
|
<div class="space-y-6">
|
|
<h2 class="text-2xl font-bold flex items-center mb-6">
|
|
<i class="bi bi-wrench mr-3 text-error"></i>Repair Settings
|
|
</h2>
|
|
|
|
<div class="form-control">
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox checkbox-lg" name="repair.enabled" id="repair.enabled">
|
|
<div>
|
|
<span class="label-text font-medium text-lg">Enable Scheduled Repair</span>
|
|
<div class="label-text-alt">Automatically repair broken symlinks and missing files</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<div class="form-control">
|
|
<label class="label" for="repair.interval">
|
|
<span class="label-text font-medium">Repair Interval</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="repair.interval" id="repair.interval" placeholder="24h">
|
|
<div class="label">
|
|
<span class="label-text-alt">How often to run repair (e.g., 24h, 1d, 03:00, or crontab)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="repair.workers">
|
|
<span class="label-text font-medium">Worker Threads</span>
|
|
</label>
|
|
<input type="number" class="input input-bordered" name="repair.workers" id="repair.workers" min="1" placeholder="40">
|
|
<div class="label">
|
|
<span class="label-text-alt">Number of concurrent repair workers</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="repair.strategy">
|
|
<span class="label-text font-medium">Repair Strategy</span>
|
|
</label>
|
|
<select class="select select-bordered" name="repair.strategy" id="repair.strategy">
|
|
<option value="per_torrent" selected>Per Torrent</option>
|
|
<option value="per_file">Per File</option>
|
|
</select>
|
|
<div class="label">
|
|
<span class="label-text-alt">How to handle repairs</span>
|
|
</div>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label" for="repair.zurg_url">
|
|
<span class="label-text font-medium">Zurg URL</span>
|
|
</label>
|
|
<input type="url" class="input input-bordered" name="repair.zurg_url" id="repair.zurg_url" placeholder="http://zurg:9999">
|
|
<div class="label">
|
|
<span class="label-text-alt">Optional Zurg instance to speed up repairs</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 lg:grid-cols-3 gap-4">
|
|
<div class="form-control">
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox" name="repair.use_webdav" id="repair.use_webdav">
|
|
<div>
|
|
<span class="label-text font-medium">Use WebDAV</span>
|
|
<div class="label-text-alt">Use internal WebDAV for repairs</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox" name="repair.auto_process" id="repair.auto_process">
|
|
<div>
|
|
<span class="label-text font-medium">Auto Process</span>
|
|
<div class="label-text-alt">Automatically delete broken symlinks and re-search</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-content hidden" data-tab-content="rclone">
|
|
<div class="space-y-6">
|
|
<h2 class="text-2xl font-bold flex items-center mb-6">
|
|
<i class="bi bi-hdd-stack mr-3 text-info"></i>Rclone Mount Settings
|
|
</h2>
|
|
|
|
<div class="form-control">
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox checkbox-lg" name="rclone.enabled" id="rclone.enabled">
|
|
<div>
|
|
<span class="label-text font-medium text-lg">Enable Mount</span>
|
|
<div class="label-text-alt">Automatically mount your debrid items</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="card bg-base-200">
|
|
<div class="card-body">
|
|
<h3 class="text-lg font-semibold mb-4 flex items-center">
|
|
<i class="bi bi-folder mr-2"></i>Mount Configuration
|
|
</h3>
|
|
<div class="grid grid-cols-3 gap-4">
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.mount_path">
|
|
<span class="label-text font-medium">Global Mount Path</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.mount_path" id="rclone.mount_path" placeholder="/mnt/decypharr">
|
|
<div class="label">
|
|
<span class="label-text-alt">Base directory where all providers will be mounted (e.g., /mnt/decypharr)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.rc_port">
|
|
<span class="label-text font-medium">RC Port</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.rc_port" id="rclone.rc_port">
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.log_level">
|
|
<span class="label-text font-medium">Log Level</span>
|
|
</label>
|
|
<select class="select select-bordered" name="rclone.log_level" id="rclone.log_level">
|
|
<option value="INFO">INFO</option>
|
|
<option value="DEBUG">DEBUG</option>
|
|
<option value="NOTICE">NOTICE</option>
|
|
<option value="ERROR">ERROR</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.uid">
|
|
<span class="label-text font-medium">User ID (PUID)</span>
|
|
</label>
|
|
<input type="number" class="input input-bordered" name="rclone.uid" id="rclone.uid" placeholder="1000" min="0">
|
|
<div class="label">
|
|
<span class="label-text-alt">User ID for mounted files (0 = current user)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.gid">
|
|
<span class="label-text font-medium">Group ID (PGID)</span>
|
|
</label>
|
|
<input type="number" class="input input-bordered" name="rclone.gid" id="rclone.gid" placeholder="1000" min="0">
|
|
<div class="label">
|
|
<span class="label-text-alt">Group ID for mounted files (0 = current group)</span>
|
|
</div>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.umask">
|
|
<span class="label-text font-medium">UMASK</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.umask" id="rclone.umask" placeholder="0022">
|
|
<div class="label">
|
|
<span class="label-text-alt">Umask</span>
|
|
</div>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.buffer_size">
|
|
<span class="label-text font-medium">Buffer Size</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.buffer_size" id="rclone.buffer_size" placeholder="10M" min="0">
|
|
<div class="label">
|
|
<span class="label-text-alt">Buffer Size(This caches to memory, be wary!!)</span>
|
|
</div>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.attr_timeout">
|
|
<span class="label-text font-medium">Attribute Caching Timeout</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.attr_timeout" id="rclone.attr_timeout" placeholder="1s">
|
|
<div class="label">
|
|
<span class="label-text-alt">How long the kernel caches the attributes (size, modification time, etc.)</span>
|
|
</div>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.transfers">
|
|
<span class="label-text font-medium">Transfers</span>
|
|
</label>
|
|
<input type="number" class="input input-bordered" name="rclone.transfers" id="rclone.transfers" placeholder="8" min="1">
|
|
<div class="label">
|
|
<span class="label-text-alt">Number of file transfers to run in parallel</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card bg-base-200">
|
|
<div class="card-body">
|
|
<h3 class="text-lg font-semibold mb-4 flex items-center">
|
|
<i class="bi bi-speedometer2 mr-2"></i>VFS Cache Settings
|
|
</h3>
|
|
<div class="grid grid-cols-1 lg:grid-cols-4 gap-4">
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.cache_dir">
|
|
<span class="label-text font-medium">Cache Directory</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.cache_dir" id="rclone.cache_dir" placeholder="/tmp/rclone">
|
|
<div class="label">
|
|
<span class="label-text-alt">Directory for rclone cache files (leave empty for system default)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.vfs_cache_mode">
|
|
<span class="label-text font-medium">VFS Cache Mode</span>
|
|
</label>
|
|
<select class="select select-bordered" name="rclone.vfs_cache_mode" id="rclone.vfs_cache_mode">
|
|
<option value="off">Off - No caching</option>
|
|
<option value="minimal">Minimal - Cache file structure only</option>
|
|
<option value="writes">Writes - Cache writes for better performance</option>
|
|
<option value="full">Full - Cache reads and writes</option>
|
|
</select>
|
|
<div class="label">
|
|
<span class="label-text-alt">VFS caching mode for performance optimization</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.vfs_cache_max_size">
|
|
<span class="label-text font-medium">VFS Cache Max Size</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.vfs_cache_max_size" id="rclone.vfs_cache_max_size" placeholder="1G">
|
|
<div class="label">
|
|
<span class="label-text-alt">Maximum cache size (e.g., 1G, 500M, leave empty for unlimited)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.vfs_cache_max_age">
|
|
<span class="label-text font-medium">VFS Cache Max Age</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.vfs_cache_max_age" id="rclone.vfs_cache_max_age" placeholder="1h">
|
|
<div class="label">
|
|
<span class="label-text-alt">Maximum age of cache entries (e.g., 1h, 30m)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.vfs_read_chunk_size">
|
|
<span class="label-text font-medium">Read Chunk Size</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.vfs_read_chunk_size" id="rclone.vfs_read_chunk_size" placeholder="128M">
|
|
<div class="label">
|
|
<span class="label-text-alt">Size of data chunks to read (e.g., 128M, 64M)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.vfs_read_chunk_size_limit">
|
|
<span class="label-text font-medium">Read Chunk Size Limit</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.vfs_read_chunk_size_limit" id="rclone.vfs_read_chunk_size_limit" placeholder="128M">
|
|
<div class="label">
|
|
<span class="label-text-alt">Limit Read Chunk Size</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.vfs_read_ahead">
|
|
<span class="label-text font-medium">VFS Read Ahead</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.vfs_read_ahead" id="rclone.vfs_read_ahead" placeholder="128k">
|
|
<div class="label">
|
|
<span class="label-text-alt">Read ahead buffer size (e.g., 128k, 256k)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.dir_cache_time">
|
|
<span class="label-text font-medium">Directory Cache Time</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.dir_cache_time" id="rclone.dir_cache_time" placeholder="5m">
|
|
<div class="label">
|
|
<span class="label-text-alt">How long to cache directory listings (e.g., 5m, 10m)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.vfs_cache_poll_interval">
|
|
<span class="label-text font-medium">VFS Cache Poll Interval</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.vfs_cache_poll_interval" id="rclone.vfs_cache_poll_interval" placeholder="1h">
|
|
<div class="label">
|
|
<span class="label-text-alt">How often VFS cache dir gets cleaned</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.vfs_cache_min_free_space">
|
|
<span class="label-text font-medium">VFS Cache Min Free Space</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.vfs_cache_min_free_space" id="rclone.vfs_cache_min_free_space" placeholder="1G">
|
|
<div class="label">
|
|
<span class="label-text-alt">Target minimum free space on the disk containing the cache</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.vfs_disk_space_total">
|
|
<span class="label-text font-medium">VFS Disk Space Total</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered" name="rclone.vfs_disk_space_total" id="rclone.vfs_disk_space_total" placeholder="1G">
|
|
<div class="label">
|
|
<span class="label-text-alt">Specify the total space of disk</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label" for="rclone.vfs_read_chunk_streams">
|
|
<span class="label-text font-medium">VFS Read Chunk Streams</span>
|
|
</label>
|
|
<input type="number" class="input input-bordered" name="rclone.vfs_read_chunk_streams" id="rclone.vfs_read_chunk_streams" placeholder="4" min="0">
|
|
<div class="label">
|
|
<span class="label-text-alt">The number of parallel streams to read at once</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card bg-base-200">
|
|
<div class="card-body">
|
|
<h3 class="text-lg font-semibold mb-4 flex items-center">
|
|
<i class="bi bi-gear mr-2"></i>Advanced Settings
|
|
</h3>
|
|
<div class="grid grid-cols-2 lg:grid-cols-3 gap-4">
|
|
<div class="form-control">
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox" name="rclone.no_modtime" id="rclone.no_modtime">
|
|
<div>
|
|
<span class="label-text font-medium">No Modification Time</span>
|
|
<div class="label-text-alt">Don't read/write modification times</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox" name="rclone.no_checksum" id="rclone.no_checksum">
|
|
<div>
|
|
<span class="label-text font-medium">No Checksum</span>
|
|
<div class="label-text-alt">Don't checksum files on upload</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label cursor-pointer justify-start gap-3" for="rclone.async_read">
|
|
<input type="checkbox" class="checkbox" name="rclone.async_read" id="rclone.async_read">
|
|
<div>
|
|
<span class="label-text font-medium">Async Read</span>
|
|
<div class="label-text-alt">Use asynchronous reads</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label cursor-pointer justify-start gap-3" for="rclone.vfs_fast_fingerprint">
|
|
<input type="checkbox" class="checkbox" name="rclone.vfs_fast_fingerprint" id="rclone.vfs_fast_fingerprint">
|
|
<div>
|
|
<span class="label-text font-medium">VFS Fast Fingerprint</span>
|
|
<div class="label-text-alt">Use fast (less accurate) fingerprints for change detection</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label cursor-pointer justify-start gap-3" for="rclone.use_mmap">
|
|
<input type="checkbox" class="checkbox" name="rclone.use_mmap" id="rclone.use_mmap">
|
|
<div>
|
|
<span class="label-text font-medium">Use Mmap</span>
|
|
<div class="label-text-alt">Use fast (less accurate) fingerprints for change detection</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div id="loadingOverlay" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden">
|
|
<div class="flex items-center justify-center h-full">
|
|
<div class="card bg-base-100 shadow-2xl">
|
|
<div class="card-body text-center">
|
|
<span class="loading loading-spinner loading-lg text-primary"></span>
|
|
<h3 class="text-lg font-semibold mt-4">Applying Configuration</h3>
|
|
<p class="text-base-content/70">Please wait while we save your settings...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Tab Navigation Handler
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
window.configManager = new ConfigManager();
|
|
|
|
const tabButtons = document.querySelectorAll('.tab-button');
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
|
|
// Tab switching function
|
|
function switchTab(targetTab) {
|
|
|
|
// Remove active class from all tabs
|
|
tabButtons.forEach(btn => {
|
|
btn.classList.remove('active');
|
|
});
|
|
|
|
// Add active class to clicked tab
|
|
const activeButton = document.querySelector(`[data-tab="${targetTab}"]`);
|
|
if (activeButton) {
|
|
activeButton.classList.add('active');
|
|
}
|
|
|
|
// Hide all tab contents
|
|
tabContents.forEach(content => {
|
|
content.classList.add('hidden');
|
|
});
|
|
|
|
// Show selected tab content
|
|
const activeContent = document.querySelector(`[data-tab-content="${targetTab}"]`);
|
|
if (activeContent) {
|
|
activeContent.classList.remove('hidden');
|
|
}
|
|
|
|
// Update URL hash for bookmarking
|
|
window.location.hash = `tab-${targetTab}`;
|
|
}
|
|
|
|
// Add click handlers to tab buttons
|
|
tabButtons.forEach(button => {
|
|
button.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const tabId = button.getAttribute('data-tab');
|
|
switchTab(tabId);
|
|
});
|
|
});
|
|
|
|
// Handle initial tab based on URL hash
|
|
const hash = window.location.hash;
|
|
if (hash.startsWith('#tab-')) {
|
|
const tabId = hash.replace('#tab-', '');
|
|
switchTab(tabId);
|
|
} else {
|
|
switchTab('general');
|
|
}
|
|
|
|
// Handle browser back/forward
|
|
window.addEventListener('hashchange', () => {
|
|
const hash = window.location.hash;
|
|
if (hash.startsWith('#tab-')) {
|
|
const tabId = hash.replace('#tab-', '');
|
|
switchTab(tabId);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Password confirmation validation
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const password = document.getElementById('auth-password');
|
|
const confirmPassword = document.getElementById('auth-password-confirm');
|
|
const indicator = document.getElementById('password-match-indicator');
|
|
|
|
function validatePasswords() {
|
|
if (!password.value && !confirmPassword.value) {
|
|
indicator.textContent = '';
|
|
indicator.className = 'label-text-alt';
|
|
return;
|
|
}
|
|
|
|
if (password.value === confirmPassword.value) {
|
|
indicator.textContent = '✓ Passwords match';
|
|
indicator.className = 'label-text-alt text-success';
|
|
} else {
|
|
indicator.textContent = '✗ Passwords do not match';
|
|
indicator.className = 'label-text-alt text-error';
|
|
}
|
|
}
|
|
|
|
password?.addEventListener('input', validatePasswords);
|
|
confirmPassword?.addEventListener('input', validatePasswords);
|
|
});
|
|
|
|
// API Token Management Functions
|
|
async function refreshAPIToken() {
|
|
const refreshBtn = document.getElementById('refresh-token-btn');
|
|
const tokenDisplay = document.getElementById('api-token-display');
|
|
|
|
// Show loading state
|
|
window.decypharrUtils.setButtonLoading(refreshBtn, true, 'Refresh Token');
|
|
|
|
try {
|
|
const response = await window.decypharrUtils.fetcher('/api/refresh-token', {
|
|
method: 'POST'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to refresh token');
|
|
}
|
|
|
|
const data = await response.json();
|
|
tokenDisplay.value = data.token;
|
|
window.decypharrUtils.createToast(data.message || 'Token refreshed successfully', 'success');
|
|
|
|
} catch (error) {
|
|
console.error('Error refreshing token:', error);
|
|
window.decypharrUtils.createToast('Failed to refresh token: ' + error.message, 'error');
|
|
} finally {
|
|
window.decypharrUtils.setButtonLoading(refreshBtn, false);
|
|
}
|
|
}
|
|
|
|
async function copyAPIToken() {
|
|
const tokenDisplay = document.getElementById('api-token-display');
|
|
const token = tokenDisplay.value;
|
|
|
|
if (!token || token === 'No token generated') {
|
|
window.decypharrUtils.createToast('No token to copy. Please refresh the token first.', 'warning');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await window.decypharrUtils.copyToClipboard(token);
|
|
} catch (error) {
|
|
console.error('Failed to copy token:', error);
|
|
window.decypharrUtils.createToast('Failed to copy token to clipboard', 'error');
|
|
}
|
|
}
|
|
|
|
async function updateAuthSettings() {
|
|
const username = document.getElementById('auth-username').value;
|
|
const password = document.getElementById('auth-password').value;
|
|
const confirmPassword = document.getElementById('auth-password-confirm').value;
|
|
const updateBtn = document.getElementById('update-auth-btn');
|
|
|
|
if (password !== confirmPassword) {
|
|
window.decypharrUtils.createToast('Passwords do not match', 'error');
|
|
return false;
|
|
}
|
|
|
|
// Show loading state
|
|
window.decypharrUtils.setButtonLoading(updateBtn, true, 'Update Authentication');
|
|
|
|
try {
|
|
const response = await window.decypharrUtils.fetcher('/api/update-auth', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
username: username,
|
|
password: password,
|
|
confirm_password: confirmPassword
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
throw new Error(errorText || 'Failed to update authentication settings');
|
|
}
|
|
|
|
const data = await response.json();
|
|
window.decypharrUtils.createToast(data.message, 'success');
|
|
|
|
// Clear password fields for security
|
|
document.getElementById('auth-password').value = '';
|
|
document.getElementById('auth-password-confirm').value = '';
|
|
|
|
return true;
|
|
|
|
} catch (error) {
|
|
console.error('Error updating auth settings:', error);
|
|
window.decypharrUtils.createToast('Failed to update authentication: ' + error.message, 'error');
|
|
return false;
|
|
} finally {
|
|
window.decypharrUtils.setButtonLoading(updateBtn, false);
|
|
}
|
|
}
|
|
</script>
|
|
{{ end }} |