feat(rclone): Google Drive entegrasyonu ekle

Dockerfile ve docker-compose yapılandırması Rclone ve FUSE için güncellendi.
Backend API'leri Rclone durumunu, ayarlarını, yetkilendirmesini ve mount işlemlerini
yönetmek için eklendi. İndirmeler tamamlandığında (Torrent, YouTube, Mail.ru)
dosyaların otomatik veya manuel olarak Google Drive'a taşınması sağlandı.
Dosya sistemi hem yerel hem de mount edilmiş GDrive yollarını destekleyecek şekilde
güncellendi. Ayarlar ve Dosyalar arayüzüne ilgili kontroller eklendi.
This commit is contained in:
2026-02-02 11:35:05 +03:00
parent e7aaea53ad
commit 0fa3a818ae
7 changed files with 1349 additions and 114 deletions

View File

@@ -5,6 +5,7 @@
const tabs = [
{ id: "general", label: "General", icon: "fa-solid fa-sliders" },
{ id: "youtube", label: "YouTube", icon: "fa-brands fa-youtube" },
{ id: "rclone", label: "Rclone", icon: "fa-solid fa-cloud" },
{ id: "advanced", label: "Advanced", icon: "fa-solid fa-gear" }
];
@@ -21,6 +22,19 @@
let error = null;
let success = null;
let rcloneStatus = null;
let rcloneLoading = false;
let rcloneSaving = false;
let rcloneAuthUrl = "";
let rcloneToken = "";
let rcloneClientId = "";
let rcloneClientSecret = "";
let rcloneAutoMove = false;
let rcloneAutoMount = false;
let rcloneRemoteName = "";
let rcloneRemotePath = "";
let rcloneMountDir = "";
async function loadCookies() {
loadingCookies = true;
error = null;
@@ -111,9 +125,136 @@
}
}
async function loadRcloneStatus() {
rcloneLoading = true;
error = null;
try {
const resp = await apiFetch("/api/rclone/status");
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const data = await resp.json();
rcloneStatus = data;
rcloneAutoMove = Boolean(data?.autoMove);
rcloneAutoMount = Boolean(data?.autoMount);
rcloneRemoteName = data?.remoteName || "";
rcloneRemotePath = data?.remotePath || "";
rcloneMountDir = data?.mountDir || "";
} catch (err) {
error = err?.message || "Rclone durumu alınamadı.";
} finally {
rcloneLoading = false;
}
}
async function saveRcloneSettings() {
if (rcloneSaving) return;
rcloneSaving = true;
error = null;
success = null;
try {
const resp = await apiFetch("/api/rclone/settings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
autoMove: rcloneAutoMove,
autoMount: rcloneAutoMount,
remoteName: rcloneRemoteName,
remotePath: rcloneRemotePath,
mountDir: rcloneMountDir
})
});
const data = await resp.json().catch(() => ({}));
if (!resp.ok || !data?.ok) {
throw new Error(data?.error || `HTTP ${resp.status}`);
}
success = "Rclone ayarları kaydedildi.";
await loadRcloneStatus();
} catch (err) {
error = err?.message || "Rclone ayarları kaydedilemedi.";
} finally {
rcloneSaving = false;
}
}
async function requestRcloneAuthUrl() {
error = null;
success = null;
try {
const resp = await apiFetch("/api/rclone/auth-url");
const data = await resp.json().catch(() => ({}));
if (!resp.ok || !data?.ok) {
throw new Error(data?.error || `HTTP ${resp.status}`);
}
rcloneAuthUrl = data.url || "";
} catch (err) {
error = err?.message || "Auth URL alınamadı.";
}
}
async function applyRcloneToken() {
if (!rcloneToken) {
error = "Token zorunlu.";
return;
}
error = null;
success = null;
try {
const resp = await apiFetch("/api/rclone/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
token: rcloneToken,
clientId: rcloneClientId,
clientSecret: rcloneClientSecret,
remoteName: rcloneRemoteName
})
});
const data = await resp.json().catch(() => ({}));
if (!resp.ok || !data?.ok) {
throw new Error(data?.error || `HTTP ${resp.status}`);
}
success = "Token kaydedildi.";
await loadRcloneStatus();
} catch (err) {
error = err?.message || "Token kaydedilemedi.";
}
}
async function startRcloneMount() {
error = null;
success = null;
try {
const resp = await apiFetch("/api/rclone/mount", { method: "POST" });
const data = await resp.json().catch(() => ({}));
if (!resp.ok || !data?.ok) {
throw new Error(data?.error || `HTTP ${resp.status}`);
}
success = "Rclone mount başlatıldı.";
await loadRcloneStatus();
} catch (err) {
error = err?.message || "Rclone mount başlatılamadı.";
}
}
async function stopRcloneMount() {
error = null;
success = null;
try {
const resp = await apiFetch("/api/rclone/unmount", { method: "POST" });
const data = await resp.json().catch(() => ({}));
if (!resp.ok || !data?.ok) {
throw new Error(data?.error || `HTTP ${resp.status}`);
}
success = "Rclone mount durduruldu.";
await loadRcloneStatus();
} catch (err) {
error = err?.message || "Rclone mount durdurulamadı.";
}
}
onMount(() => {
loadCookies();
loadYoutubeSettings();
loadRcloneStatus();
});
function formatDate(ts) {
@@ -236,6 +377,135 @@
</div>
{:else if activeTab === "general"}
<div class="card muted">Genel ayarlar burada yer alacak.</div>
{:else if activeTab === "rclone"}
<div class="card">
<div class="card-header">
<div class="title">
<i class="fa-solid fa-cloud"></i>
<span>Google Drive (Rclone)</span>
</div>
</div>
<div class="field inline compact left-align">
<label class="checkbox-row">
<input
type="checkbox"
bind:checked={rcloneAutoMove}
disabled={rcloneLoading || rcloneSaving}
/>
<span>İndirince otomatik taşı</span>
</label>
<label class="checkbox-row">
<input
type="checkbox"
bind:checked={rcloneAutoMount}
disabled={rcloneLoading || rcloneSaving}
/>
<span>Başlangıçta otomatik mount</span>
</label>
</div>
<div class="field inline compact left-align">
<div class="inline-field">
<label for="rclone-remote">Remote adı</label>
<input
id="rclone-remote"
type="text"
bind:value={rcloneRemoteName}
disabled={rcloneLoading || rcloneSaving}
/>
</div>
<div class="inline-field">
<label for="rclone-path">Drive klasörü</label>
<input
id="rclone-path"
type="text"
bind:value={rcloneRemotePath}
disabled={rcloneLoading || rcloneSaving}
/>
</div>
</div>
<div class="field">
<label for="rclone-mount">Mount dizini</label>
<input
id="rclone-mount"
type="text"
bind:value={rcloneMountDir}
disabled={rcloneLoading || rcloneSaving}
/>
</div>
<div class="actions">
<button class="btn" on:click={loadRcloneStatus} disabled={rcloneLoading || rcloneSaving}>
<i class="fa-solid fa-rotate"></i> Yenile
</button>
<button class="btn primary" on:click={saveRcloneSettings} disabled={rcloneLoading || rcloneSaving}>
<i class="fa-solid fa-floppy-disk"></i> Kaydet
</button>
</div>
<div class="field">
<label>Yetkilendirme</label>
<div class="actions">
<button class="btn" on:click={requestRcloneAuthUrl}>
<i class="fa-solid fa-link"></i> Auth URL al
</button>
</div>
{#if rcloneAuthUrl}
<input type="text" readonly value={rcloneAuthUrl} />
{/if}
</div>
<div class="field">
<label for="rclone-client-id">Client ID (opsiyonel)</label>
<input id="rclone-client-id" type="text" bind:value={rcloneClientId} />
</div>
<div class="field">
<label for="rclone-client-secret">Client Secret (opsiyonel)</label>
<input id="rclone-client-secret" type="text" bind:value={rcloneClientSecret} />
</div>
<div class="field">
<label for="rclone-token">Token</label>
<textarea id="rclone-token" bind:value={rcloneToken} placeholder="rclone authorize çıktısındaki token JSON'u"></textarea>
</div>
<div class="actions">
<button class="btn" on:click={applyRcloneToken}>
<i class="fa-solid fa-key"></i> Token Kaydet
</button>
<button class="btn" on:click={startRcloneMount}>
<i class="fa-solid fa-play"></i> Mount Başlat
</button>
<button class="btn" on:click={stopRcloneMount}>
<i class="fa-solid fa-stop"></i> Mount Durdur
</button>
</div>
{#if rcloneStatus}
<div class="card muted" style="margin-top:10px;">
<div>Enabled: {rcloneStatus.enabled ? "Evet" : "Hayır"}</div>
<div>Mounted: {rcloneStatus.mounted ? "Evet" : "Hayır"}</div>
<div>Remote: {rcloneStatus.remoteConfigured ? "Hazır" : "Eksik"}</div>
{#if rcloneStatus.lastError}
<div>Son hata: {rcloneStatus.lastError}</div>
{/if}
</div>
{/if}
{#if error}
<div class="alert error" style="margin-top:10px;">
<i class="fa-solid fa-circle-exclamation"></i>
{error}
</div>
{/if}
{#if success}
<div class="alert success" style="margin-top:10px;">
<i class="fa-solid fa-circle-check"></i>
{success}
</div>
{/if}
</div>
{:else if activeTab === "advanced"}
<div class="card muted">Gelişmiş ayarlar burada yer alacak.</div>
{/if}