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:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user