Compare commits
2 Commits
dc5f7fa5c8
...
dcd66fdd11
| Author | SHA1 | Date | |
|---|---|---|---|
| dcd66fdd11 | |||
| ce1693cf4e |
@@ -51,7 +51,7 @@ Ardından http://localhost:3001
|
||||
- `QBIT_BASE_URL`, `QBIT_USERNAME`, `QBIT_PASSWORD`
|
||||
- `APP_USERNAME`, `APP_PASSWORD`, `JWT_SECRET`
|
||||
- `POLL_INTERVAL_MS`, `ENFORCE_INTERVAL_MS`, `DEFAULT_DELAY_MS`, `MAX_LOOP_LIMIT`
|
||||
- `WEB_ALLOWED_HOSTS` (ör: `localhost,qbuffer.bee`)
|
||||
- `WEB_ALLOWED_HOSTS` (ör: `localhost,qbuffer.bee,qbuffer.panda`)
|
||||
|
||||
## Klasör Yapısı
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ const router = Router();
|
||||
const ruleSchema = z.object({
|
||||
tags: z.array(z.string().min(1)).min(1),
|
||||
seedLimitSeconds: z.number().int().min(60).max(60 * 60 * 24 * 365),
|
||||
deleteFiles: z.boolean().optional(),
|
||||
});
|
||||
|
||||
router.get("/rules", async (_req, res) => {
|
||||
@@ -27,6 +28,7 @@ router.post("/rules", async (req, res) => {
|
||||
id: randomUUID(),
|
||||
tags: parsed.data.tags,
|
||||
seedLimitSeconds: parsed.data.seedLimitSeconds,
|
||||
deleteFiles: parsed.data.deleteFiles ?? true,
|
||||
createdAt: nowIso(),
|
||||
};
|
||||
db.timerRules = [...(db.timerRules ?? []), rule];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export interface TimerRuleInput {
|
||||
tags: string[];
|
||||
seedLimitSeconds: number;
|
||||
deleteFiles?: boolean;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export const startTimerWorker = (qbit: QbitClient, intervalMs: number) => {
|
||||
}
|
||||
|
||||
try {
|
||||
await qbit.deleteTorrent(torrent.hash, true);
|
||||
await qbit.deleteTorrent(torrent.hash, matchingRule.deleteFiles ?? true);
|
||||
} catch (error) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ export interface TimerRule {
|
||||
id: string;
|
||||
tags: string[];
|
||||
seedLimitSeconds: number;
|
||||
deleteFiles: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,11 @@ const formatCountdown = (seconds: number) => {
|
||||
return `${days}g ${pad(hours)}:${pad(minutes)}:${pad(secs)}`;
|
||||
};
|
||||
|
||||
const formatAddedOn = (addedOn?: number) => {
|
||||
if (!Number.isFinite(addedOn)) return "Bilinmiyor";
|
||||
return new Date(addedOn * 1000).toLocaleString();
|
||||
};
|
||||
|
||||
const trackerLabel = (tracker?: string) => {
|
||||
if (!tracker) return "Bilinmiyor";
|
||||
try {
|
||||
@@ -91,6 +96,7 @@ export const TimerPage = () => {
|
||||
const [seedUnit, setSeedUnit] = useState<(typeof unitOptions)[number]["value"]>(
|
||||
"weeks"
|
||||
);
|
||||
const [deleteFiles, setDeleteFiles] = useState(true);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const pushAlert = useUiStore((s) => s.pushAlert);
|
||||
const [nowTick, setNowTick] = useState(() => Date.now());
|
||||
@@ -225,9 +231,11 @@ export const TimerPage = () => {
|
||||
const response = await api.post("/api/timer/rules", {
|
||||
tags: selectedTags,
|
||||
seedLimitSeconds,
|
||||
deleteFiles,
|
||||
});
|
||||
setTimerRules([response.data, ...timerRules]);
|
||||
setSelectedTags([]);
|
||||
setDeleteFiles(true);
|
||||
pushAlert({
|
||||
title: "Kural kaydedildi",
|
||||
description: "Timer kuralı aktif edildi.",
|
||||
@@ -322,6 +330,9 @@ export const TimerPage = () => {
|
||||
.filter(Boolean)
|
||||
.join(", ") || "-"}
|
||||
</div>
|
||||
<div className="truncate">
|
||||
Added: {formatAddedOn(torrent.added_on)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -439,6 +450,20 @@ export const TimerPage = () => {
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<label className="flex items-start gap-2 text-sm text-slate-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={deleteFiles}
|
||||
onChange={(event) => setDeleteFiles(event.target.checked)}
|
||||
className="mt-1 h-4 w-4 rounded border-slate-300 text-slate-900"
|
||||
/>
|
||||
<span>
|
||||
Dosyayı Disk'ten Kaldır
|
||||
<span className="block text-xs text-slate-500">
|
||||
İşaretli değilse torrent sadece qBittorrent'tan kaldırılır, dosya disk üzerinde kalır.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
<Button onClick={handleSaveRule} disabled={busy}>
|
||||
{busy ? "Kaydediliyor..." : "Kuralı Kaydet"}
|
||||
</Button>
|
||||
@@ -464,6 +489,10 @@ export const TimerPage = () => {
|
||||
</div>
|
||||
<div className="text-xs text-slate-500">
|
||||
Seed limiti: {formatDuration(rule.seedLimitSeconds)}
|
||||
{" • "}
|
||||
{rule.deleteFiles ?? true
|
||||
? "Silme: Disk + qBittorrent"
|
||||
: "Silme: Sadece qBittorrent"}
|
||||
</div>
|
||||
</div>
|
||||
<AlertDialog>
|
||||
|
||||
@@ -54,6 +54,7 @@ export interface TimerRule {
|
||||
id: string;
|
||||
tags: string[];
|
||||
seedLimitSeconds: number;
|
||||
deleteFiles?: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user