From d9ed85ad0cdf050b84d69f2387c234d2105250f8 Mon Sep 17 00:00:00 2001 From: wisecolt Date: Sun, 4 Jan 2026 02:43:17 +0300 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=C3=A7al=C4=B1=C5=9Fan=20durumu=20v?= =?UTF-8?q?e=20profil=20ad=C4=B1=20g=C3=B6stergeleri=20ekle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Torrent tablosunda aktif profil adı ve durum ikonları gösterilir. Döngü kurulum kartında çalışan profil durumu görüntülenir ve durdurma/çalıştırma butonu duruma göre değişir. Layout oranları ve responsive davranış iyileştirilir. --- .../web/src/components/loop/LoopSetupCard.tsx | 60 +++++-------- .../torrents/TorrentDetailsCard.tsx | 2 +- .../src/components/torrents/TorrentTable.tsx | 89 ++++++++++++++----- apps/web/src/pages/DashboardPage.tsx | 10 ++- apps/web/src/pages/TimerPage.tsx | 8 +- 5 files changed, 100 insertions(+), 69 deletions(-) diff --git a/apps/web/src/components/loop/LoopSetupCard.tsx b/apps/web/src/components/loop/LoopSetupCard.tsx index 8971dae..4099654 100644 --- a/apps/web/src/components/loop/LoopSetupCard.tsx +++ b/apps/web/src/components/loop/LoopSetupCard.tsx @@ -5,7 +5,7 @@ import { Input } from "../ui/Input"; import { Button } from "../ui/Button"; import { api } from "../../api/client"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faCircleInfo, faPen, faPlay, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { faCircleInfo, faPlay, faStop, faTrash } from "@fortawesome/free-solid-svg-icons"; import { AlertDialog, AlertDialogAction, @@ -31,6 +31,7 @@ export const LoopSetupCard = () => { const selectedHash = useAppStore((s) => s.selectedHash); const loopForm = useAppStore((s) => s.loopForm); const setLoopForm = useAppStore((s) => s.setLoopForm); + const jobs = useAppStore((s) => s.jobs); const pushAlert = useUiStore((s) => s.pushAlert); const [profiles, setProfiles] = useState([]); @@ -38,7 +39,20 @@ export const LoopSetupCard = () => { const [allowIp, setAllowIp] = useState(loopForm.allowIp || ""); const [delayMs, setDelayMs] = useState(loopForm.delayMs ?? 3000); const [targetLoops, setTargetLoops] = useState(loopForm.targetLoops ?? 3); - const [editingId, setEditingId] = useState(null); + const isRunning = Boolean(selectedHash && jobs.some((job) => job.torrentHash === selectedHash && job.status === "RUNNING")); + + const isRunningPreset = (profile: Profile) => { + if (!selectedHash) return false; + const job = jobs.find((j) => j.torrentHash === selectedHash && j.status === "RUNNING"); + if (!job) return false; + return ( + job.allowIp === profile.allowIp && + job.delayMs === profile.delayMs && + job.targetLoops === profile.targetLoops + ); + }; + + const formatDelay = (ms: number) => `${Math.round(ms / 60000)} dk`; const loadProfiles = async () => { const response = await api.get("/api/profiles"); @@ -71,18 +85,7 @@ export const LoopSetupCard = () => { return; } try { - if (editingId) { - const response = await api.put(`/api/profiles/${editingId}`, payload); - setProfiles((prev) => - prev.map((profile) => (profile.id === editingId ? response.data : profile)) - ); - pushAlert({ - title: "Setup güncellendi", - description: "Kaydedilen setup güncellendi.", - variant: "success", - }); - setEditingId(null); - } else { + { const response = await api.post("/api/profiles", payload); setProfiles((prev) => [...prev, response.data]); pushAlert({ @@ -106,19 +109,6 @@ export const LoopSetupCard = () => { } }; - const startEdit = (profile: Profile) => { - setEditingId(profile.id); - setName(profile.name); - setAllowIp(profile.allowIp); - setDelayMs(profile.delayMs); - setTargetLoops(profile.targetLoops); - setLoopForm({ - allowIp: profile.allowIp, - delayMs: profile.delayMs, - targetLoops: profile.targetLoops, - }); - }; - const applyProfile = async (profile: Profile) => { if (!selectedHash) { pushAlert({ @@ -234,25 +224,17 @@ export const LoopSetupCard = () => {
{profile.name}
- {profile.allowIp} • {profile.targetLoops} loops • {profile.delayMs} ms + {profile.allowIp} • {profile.targetLoops} loops • {formatDelay(profile.delayMs)}
- diff --git a/apps/web/src/components/torrents/TorrentDetailsCard.tsx b/apps/web/src/components/torrents/TorrentDetailsCard.tsx index 7dcb7d5..06de217 100644 --- a/apps/web/src/components/torrents/TorrentDetailsCard.tsx +++ b/apps/web/src/components/torrents/TorrentDetailsCard.tsx @@ -79,7 +79,7 @@ export const TorrentDetailsCard = () => {
- Hash: {torrent.hash} + Hash: {torrent.hash}
diff --git a/apps/web/src/components/torrents/TorrentTable.tsx b/apps/web/src/components/torrents/TorrentTable.tsx index 3d6d6bf..0d1c356 100644 --- a/apps/web/src/components/torrents/TorrentTable.tsx +++ b/apps/web/src/components/torrents/TorrentTable.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { useAppStore } from "../../store/useAppStore"; import { Card, CardContent, CardHeader, CardTitle } from "../ui/Card"; import { Input } from "../ui/Input"; @@ -15,21 +15,73 @@ import { AlertDialogTrigger, } from "../ui/AlertDialog"; import { useUiStore } from "../../store/useUiStore"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faBolt, faCloudArrowDown, faCloudArrowUp, faHourglassHalf } from "@fortawesome/free-solid-svg-icons"; + +interface Profile { + id: string; + name: string; + allowIp: string; + delayMs: number; + targetLoops: number; +} const formatSpeed = (bytesPerSec: number) => { + if (bytesPerSec <= 0) { + return "00.0 KB/s"; + } const kb = bytesPerSec / 1024; if (kb >= 1024) { return `${(kb / 1024).toFixed(1)} MB/s`; } - return `${Math.round(kb)} kB/s`; + return `${kb.toFixed(1)} KB/s`; }; export const TorrentTable = () => { const torrents = useAppStore((s) => s.torrents); const selected = useAppStore((s) => s.selectedHash); + const jobs = useAppStore((s) => s.jobs); const selectHash = useAppStore((s) => s.selectHash); const pushAlert = useUiStore((s) => s.pushAlert); const [query, setQuery] = useState(""); + const [profiles, setProfiles] = useState([]); + + const loadProfiles = async () => { + const response = await api.get("/api/profiles"); + setProfiles(response.data); + }; + + useEffect(() => { + loadProfiles(); + }, []); + + const renderState = (state: string) => { + const value = state.toLowerCase(); + if (value.includes("forced")) { + return ; + } + if (value.includes("stalled")) { + return ; + } + if (value.includes("downloading") || value.includes("metadl")) { + return ; + } + if (value.includes("uploading") || value.includes("up")) { + return ; + } + return {state}; + }; + + const getProfileName = (hash: string) => { + const job = jobs.find((j) => j.torrentHash === hash); + if (!job) return null; + const profile = profiles.find((p) => + p.allowIp === job.allowIp && + p.delayMs === job.delayMs && + p.targetLoops === job.targetLoops + ); + return profile?.name ?? null; + }; const filtered = useMemo(() => { return torrents @@ -103,40 +155,39 @@ export const TorrentTable = () => { {filtered.map((torrent) => (
selectHash(torrent.hash)} > -
+
{torrent.name}
-
+
- {Math.round(torrent.progress * 100)}% - {formatSpeed(torrent.dlspeed)} - - {torrent.state} + {Math.round(torrent.progress * 100)}% + {formatSpeed(torrent.dlspeed)} + + {renderState(torrent.state)} + {getProfileName(torrent.hash) && ( + {getProfileName(torrent.hash)} + )}