diff --git a/client/src/components/Sidebar.svelte b/client/src/components/Sidebar.svelte
index f41c026..9b5bd24 100644
--- a/client/src/components/Sidebar.svelte
+++ b/client/src/components/Sidebar.svelte
@@ -1,31 +1,119 @@
+
+
+
+
+
+
+
+ {#if diskSpace.usedFormatted && diskSpace.totalFormatted}
+ {diskSpace.usedFormatted} / {diskSpace.totalFormatted}
+ {:else}
+ {diskSpace.usedGB} GB / {diskSpace.totalGB} GB
+ {/if}
+
+
+
+
+
diff --git a/client/src/styles/main.css b/client/src/styles/main.css
index 223b2fc..4be9eb3 100644
--- a/client/src/styles/main.css
+++ b/client/src/styles/main.css
@@ -31,13 +31,14 @@ body,
.app {
display: grid;
grid-template-columns: 220px 1fr;
- height: 100%;
+ height: 100vh;
}
.content {
display: flex;
flex-direction: column;
- height: 100%;
+ height: 100vh;
+ overflow-y: auto;
}
/* =======================================================
@@ -48,6 +49,9 @@ body,
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
+ height: 100vh;
+ position: sticky;
+ top: 0;
}
.sidebar .logo {
@@ -59,6 +63,7 @@ body,
.sidebar .menu {
padding-top: 6px;
+ flex: 1;
}
.sidebar .menu .item {
@@ -603,3 +608,85 @@ img.thumb.loaded {
gap: 6px;
}
}
+
+/* =======================================================
+ 💾 DISK SPACE
+ ======================================================= */
+.sidebar .disk-space {
+ margin-top: auto;
+ padding: 16px;
+ border-top: 1px solid var(--border);
+}
+
+.sidebar .disk-space-header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 12px;
+ color: #222;
+ font-weight: 600;
+ font-size: 14px;
+}
+
+.sidebar .disk-space-header .icon {
+ width: 16px;
+ text-align: center;
+ color: #333;
+}
+
+.sidebar .disk-space-title {
+ flex: 1;
+}
+
+.sidebar .disk-space-info {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.sidebar .disk-space-text {
+ font-size: 12px;
+ color: #666;
+ text-align: left;
+ display: flex;
+ align-items: center;
+}
+
+.sidebar .disk-space-values {
+ font-weight: 500;
+ color: #333;
+}
+
+.sidebar .disk-space-bar-container {
+ width: 100%;
+ height: 8px;
+ background: #e5e5e5;
+ border-radius: 99px;
+ overflow: hidden;
+}
+
+.sidebar .disk-space-bar {
+ width: 100%;
+ height: 100%;
+ position: relative;
+}
+
+.sidebar .disk-space-bar-fill {
+ height: 100%;
+ background: linear-gradient(90deg, #f5b333 0%, #e2a62f 100%);
+ border-radius: 99px;
+ transition: width 0.3s ease;
+}
+
+/* Disk space renkleri - kullanım oranına göre */
+.sidebar .disk-space-bar-fill.low {
+ background: linear-gradient(90deg, #4caf50 0%, #388e3c 100%);
+}
+
+.sidebar .disk-space-bar-fill.medium {
+ background: linear-gradient(90deg, #ff9800 0%, #f57c00 100%);
+}
+
+.sidebar .disk-space-bar-fill.high {
+ background: linear-gradient(90deg, #f44336 0%, #d32f2f 100%);
+}
diff --git a/server/server.js b/server/server.js
index adb57d2..62b9acb 100644
--- a/server/server.js
+++ b/server/server.js
@@ -9,6 +9,7 @@ import { WebSocketServer } from "ws";
import { fileURLToPath } from "url";
import { exec } from "child_process";
import crypto from "crypto"; // 🔒 basit token üretimi için
+import { getSystemDiskInfo } from "./utils/diskSpace.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -2082,6 +2083,20 @@ function broadcastFileUpdate(rootFolder) {
wss.clients.forEach((c) => c.readyState === 1 && c.send(data));
}
+function broadcastDiskSpace() {
+ if (!wss) return;
+ getSystemDiskInfo(DOWNLOAD_DIR).then(diskInfo => {
+ console.log("🔄 Broadcasting disk space:", diskInfo);
+ const data = JSON.stringify({
+ type: "diskSpace",
+ data: diskInfo
+ });
+ wss.clients.forEach((c) => c.readyState === 1 && c.send(data));
+ }).catch(err => {
+ console.error("❌ Disk space broadcast error:", err.message);
+ });
+}
+
function broadcastSnapshot() {
if (!wss) return;
const data = JSON.stringify({ type: "progress", torrents: snapshot() });
@@ -2342,6 +2357,9 @@ app.post("/api/transfer", requireAuth, upload.single("torrent"), (req, res) => {
upsertInfoFile(entry.savePath, infoUpdate);
broadcastFileUpdate(rootFolder);
+
+ // Torrent tamamlandığında disk space bilgisini güncelle
+ broadcastDiskSpace();
broadcastSnapshot();
});
@@ -2755,6 +2773,9 @@ app.delete("/api/file", requireAuth, (req, res) => {
torrents.delete(matchedInfoHash);
console.log(`🧹 Torrent kaydı da temizlendi: ${matchedInfoHash}`);
broadcastSnapshot();
+
+ // Torrent silindiğinde disk space bilgisini güncelle
+ broadcastDiskSpace();
});
} else {
broadcastSnapshot();
@@ -3501,7 +3522,18 @@ const server = app.listen(PORT, () =>
wss = new WebSocketServer({ server });
wss.on("connection", (ws) => {
+ console.log("🔌 New WebSocket connection established");
ws.send(JSON.stringify({ type: "progress", torrents: snapshot() }));
+ // Bağlantı kurulduğunda disk space bilgisi gönder
+ broadcastDiskSpace();
+
+ ws.on("close", () => {
+ console.log("🔌 WebSocket connection closed");
+ });
+
+ ws.on("error", (error) => {
+ console.error("🔌 WebSocket error:", error);
+ });
});
// --- ⏱️ Her 2 saniyede bir aktif torrent durumu yayınla ---
@@ -3511,6 +3543,27 @@ setInterval(() => {
}
}, 2000);
+// --- ⏱️ Her 30 saniyede bir disk space bilgisi yayınla ---
+setInterval(() => {
+ broadcastDiskSpace();
+}, 30000);
+
+// --- Disk space bilgisi ---
+app.get("/api/disk-space", requireAuth, async (req, res) => {
+ try {
+ // Downloads klasörü yoksa oluştur
+ if (!fs.existsSync(DOWNLOAD_DIR)) {
+ fs.mkdirSync(DOWNLOAD_DIR, { recursive: true });
+ }
+
+ const diskInfo = await getSystemDiskInfo(DOWNLOAD_DIR);
+ res.json(diskInfo);
+ } catch (err) {
+ console.error("❌ Disk space error:", err.message);
+ res.status(500).json({ error: err.message });
+ }
+});
+
client.on("error", (err) => {
if (!String(err).includes("uTP"))
console.error("WebTorrent error:", err.message);
diff --git a/server/utils/diskSpace.js b/server/utils/diskSpace.js
new file mode 100644
index 0000000..9d128a9
--- /dev/null
+++ b/server/utils/diskSpace.js
@@ -0,0 +1,243 @@
+import fs from 'fs';
+import { exec } from 'child_process';
+import { promisify } from 'util';
+
+const execAsync = promisify(exec);
+
+/**
+ * Disk alanı bilgilerini hesaplar
+ * @param {string} path - Kontrol edilecek dizin yolu
+ * @returns {Promise