fix(server): hata yönetimini ve dayanıklılığı iyileştir
Loop scheduler ve timer worker için hata yakalama ekle. qBit client'ta geçici ağ hatalarını tanıyarak login durumunu sıfırla. Scheduler hatalarında durum güncellemesi gönder ve timer worker crash önle.
This commit is contained in:
27
apps/web/src/components/ui/ScrollArea.tsx
Normal file
27
apps/web/src/components/ui/ScrollArea.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import * as React from "react";
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={clsx("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full overflow-y-auto rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollAreaPrimitive.Scrollbar
|
||||
orientation="vertical"
|
||||
className="flex w-2.5 touch-none select-none rounded-full bg-transparent p-0.5 opacity-100"
|
||||
>
|
||||
<ScrollAreaPrimitive.Thumb className="relative flex-1 rounded-full bg-slate-200" />
|
||||
</ScrollAreaPrimitive.Scrollbar>
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
));
|
||||
|
||||
ScrollArea.displayName = "ScrollArea";
|
||||
@@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/Card";
|
||||
import { Button } from "../components/ui/Button";
|
||||
import { Input } from "../components/ui/Input";
|
||||
import { ScrollArea } from "../components/ui/ScrollArea";
|
||||
import { api } from "../api/client";
|
||||
import { useAppStore } from "../store/useAppStore";
|
||||
import { useUiStore } from "../store/useUiStore";
|
||||
@@ -360,13 +361,21 @@ export const TimerPage = () => {
|
||||
<SelectItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
onClick={() => {
|
||||
onPointerDown={() => {
|
||||
if (option.value === sortKey) {
|
||||
setSortDirection((current) =>
|
||||
current === "asc" ? "desc" : "asc"
|
||||
);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (option.value !== sortKey) return;
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
setSortDirection((current) =>
|
||||
current === "asc" ? "desc" : "asc"
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
@@ -435,39 +444,43 @@ export const TimerPage = () => {
|
||||
Silinen Torrent Logları
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<CardContent className="space-y-3 overflow-hidden">
|
||||
{timerLogs.length === 0 ? (
|
||||
<div className="text-sm text-slate-500">Henüz log yok.</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{timerLogs.map((log) => (
|
||||
<div
|
||||
key={log.id}
|
||||
className="rounded-lg border border-slate-200 bg-white px-3 py-2"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-slate-900">
|
||||
{log.name}
|
||||
<div className="overflow-hidden" style={{ height: 560 }}>
|
||||
<ScrollArea className="h-full w-full" type="always">
|
||||
<div className="space-y-3 pr-3">
|
||||
{timerLogs.map((log) => (
|
||||
<div
|
||||
key={log.id}
|
||||
className="rounded-lg border border-slate-200 bg-white px-3 py-2"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-slate-900">
|
||||
{log.name}
|
||||
</div>
|
||||
<div className="text-xs text-slate-500">
|
||||
{formatBytes(log.sizeBytes)} •{" "}
|
||||
{trackerLabel(log.tracker)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-slate-400">
|
||||
{new Date(log.deletedAt).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-slate-500">
|
||||
{formatBytes(log.sizeBytes)} •{" "}
|
||||
{trackerLabel(log.tracker)}
|
||||
<div className="mt-2 flex flex-wrap gap-2 text-xs text-slate-600">
|
||||
<span>Seed: {formatDuration(log.seedingTimeSeconds)}</span>
|
||||
<span>Upload: {formatBytes(log.uploadedBytes)}</span>
|
||||
{log.tags?.length ? (
|
||||
<span>Tags: {log.tags.join(", ")}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-slate-400">
|
||||
{new Date(log.deletedAt).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap gap-2 text-xs text-slate-600">
|
||||
<span>Seed: {formatDuration(log.seedingTimeSeconds)}</span>
|
||||
<span>Upload: {formatBytes(log.uploadedBytes)}</span>
|
||||
{log.tags?.length ? (
|
||||
<span>Tags: {log.tags.join(", ")}</span>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user