feat(rclone): RC API ilerleme takibi ve conf editörü ekle
- Rclone RC API kullanılarak dosya yüklemelerinde anlık ilerleme çubuğu eklendi. - Arayüz üzerinden `rclone.conf` dosyası düzenlenebilir hale getirildi. - VFS cache boyutu/yaş sınırları ve otomatik temizleme ayarı eklendi. - Manuel yetkilendirme alanları kaldırıldı.
This commit is contained in:
@@ -25,15 +25,11 @@
|
||||
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 = "";
|
||||
let rcloneCacheCleanMinutes = 0;
|
||||
let rcloneConfText = "";
|
||||
let rcloneConfVisible = false;
|
||||
|
||||
async function loadCookies() {
|
||||
loadingCookies = true;
|
||||
@@ -135,9 +131,7 @@
|
||||
rcloneStatus = data;
|
||||
rcloneAutoMove = Boolean(data?.autoMove);
|
||||
rcloneAutoMount = Boolean(data?.autoMount);
|
||||
rcloneRemoteName = data?.remoteName || "";
|
||||
rcloneRemotePath = data?.remotePath || "";
|
||||
rcloneMountDir = data?.mountDir || "";
|
||||
rcloneCacheCleanMinutes = Number(data?.cacheCleanMinutes) || 0;
|
||||
} catch (err) {
|
||||
error = err?.message || "Rclone durumu alınamadı.";
|
||||
} finally {
|
||||
@@ -145,6 +139,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRcloneConf() {
|
||||
try {
|
||||
const resp = await apiFetch("/api/rclone/conf");
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (!resp.ok || !data?.ok) {
|
||||
throw new Error(data?.error || `HTTP ${resp.status}`);
|
||||
}
|
||||
rcloneConfText = data.content || "";
|
||||
} catch (err) {
|
||||
error = err?.message || "rclone.conf okunamadı.";
|
||||
}
|
||||
}
|
||||
|
||||
async function saveRcloneSettings() {
|
||||
if (rcloneSaving) return;
|
||||
rcloneSaving = true;
|
||||
@@ -157,15 +164,22 @@
|
||||
body: JSON.stringify({
|
||||
autoMove: rcloneAutoMove,
|
||||
autoMount: rcloneAutoMount,
|
||||
remoteName: rcloneRemoteName,
|
||||
remotePath: rcloneRemotePath,
|
||||
mountDir: rcloneMountDir
|
||||
cacheCleanMinutes: rcloneCacheCleanMinutes
|
||||
})
|
||||
});
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (!resp.ok || !data?.ok) {
|
||||
throw new Error(data?.error || `HTTP ${resp.status}`);
|
||||
}
|
||||
const confResp = await apiFetch("/api/rclone/conf", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ content: rcloneConfText })
|
||||
});
|
||||
const confData = await confResp.json().catch(() => ({}));
|
||||
if (!confResp.ok || !confData?.ok) {
|
||||
throw new Error(confData?.error || `HTTP ${confResp.status}`);
|
||||
}
|
||||
success = "Rclone ayarları kaydedildi.";
|
||||
await loadRcloneStatus();
|
||||
} catch (err) {
|
||||
@@ -175,50 +189,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -251,10 +221,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanRcloneCache() {
|
||||
error = null;
|
||||
success = null;
|
||||
try {
|
||||
const resp = await apiFetch("/api/rclone/cache/clean", { method: "POST" });
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (!resp.ok || !data?.ok) {
|
||||
throw new Error(data?.error || `HTTP ${resp.status}`);
|
||||
}
|
||||
success = "Rclone cache temizlendi.";
|
||||
} catch (err) {
|
||||
error = err?.message || "Rclone cache temizlenemedi.";
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
loadCookies();
|
||||
loadYoutubeSettings();
|
||||
loadRcloneStatus();
|
||||
loadRcloneConf();
|
||||
});
|
||||
|
||||
function formatDate(ts) {
|
||||
@@ -318,7 +304,7 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<div class="actions left">
|
||||
<button class="btn" on:click={loadYoutubeSettings} disabled={loadingYtSettings || savingYtSettings}>
|
||||
<i class="fa-solid fa-rotate"></i> Yenile
|
||||
</button>
|
||||
@@ -407,73 +393,47 @@
|
||||
|
||||
<div class="field inline compact left-align">
|
||||
<div class="inline-field">
|
||||
<label for="rclone-remote">Remote adı</label>
|
||||
<label for="rclone-cache-clean">Cache temizleme (dakika)</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}
|
||||
id="rclone-cache-clean"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
bind:value={rcloneCacheCleanMinutes}
|
||||
disabled={rcloneLoading || rcloneSaving}
|
||||
/>
|
||||
</div>
|
||||
<button class="btn" on:click={cleanRcloneCache}>
|
||||
<i class="fa-solid fa-broom"></i> Clean Cache
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="rclone-mount">Mount dizini</label>
|
||||
<input
|
||||
id="rclone-mount"
|
||||
type="text"
|
||||
bind:value={rcloneMountDir}
|
||||
disabled={rcloneLoading || rcloneSaving}
|
||||
/>
|
||||
<label>rclone.conf</label>
|
||||
<div class="password-field">
|
||||
<textarea
|
||||
class="conf-textarea {rcloneConfVisible ? '' : 'masked'}"
|
||||
bind:value={rcloneConfText}
|
||||
placeholder="rclone.conf içeriğini yapıştırın"
|
||||
></textarea>
|
||||
<button
|
||||
class="eye-btn"
|
||||
type="button"
|
||||
on:click={() => (rcloneConfVisible = !rcloneConfVisible)}
|
||||
aria-label="Gizli/Görünür"
|
||||
>
|
||||
<i class={rcloneConfVisible ? "fa-solid fa-eye-slash" : "fa-solid fa-eye"}></i>
|
||||
</button>
|
||||
</div>
|
||||
</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>
|
||||
@@ -707,4 +667,32 @@
|
||||
background: #e5ffe7;
|
||||
color: #0f7a1f;
|
||||
}
|
||||
|
||||
.password-field {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.conf-textarea {
|
||||
width: 100%;
|
||||
min-height: 180px;
|
||||
resize: vertical;
|
||||
font-family: "Courier New", monospace;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.conf-textarea.masked {
|
||||
-webkit-text-security: disc;
|
||||
text-security: disc;
|
||||
}
|
||||
|
||||
.eye-btn {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 8px;
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
padding: 6px 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user