feat: SSR pour toutes les cards de la page Settings

Toutes les configurations (Prowlarr, qBittorrent, Telegram, Anilist,
Komga, metadata providers, status mappings) sont maintenant récupérées
côté serveur dans page.tsx et passées en props aux cards.

Supprime ~10 fetchs client useEffect au chargement, élimine les
layout shifts et réduit le temps de rendu initial.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 09:11:12 +01:00
parent 432bb519ab
commit e7295a371d
9 changed files with 111 additions and 173 deletions

View File

@@ -20,9 +20,19 @@ interface SettingsPageProps {
initialThumbnailStats: ThumbnailStats; initialThumbnailStats: ThumbnailStats;
users: UserDto[]; users: UserDto[];
initialTab?: string; initialTab?: string;
initialProwlarr: Record<string, unknown> | null;
initialQbittorrent: Record<string, unknown> | null;
initialTorrentImport: Record<string, unknown> | null;
initialTelegram: Record<string, unknown> | null;
initialAnilist: Record<string, unknown> | null;
initialKomga: Record<string, unknown> | null;
initialMetadataProviders: Record<string, unknown> | null;
initialStatusMappings: Record<string, unknown>[];
initialSeriesStatuses: string[];
initialProviderStatuses: string[];
} }
export default function SettingsPage({ initialSettings, initialCacheStats, initialThumbnailStats, users, initialTab }: SettingsPageProps) { export default function SettingsPage({ initialSettings, initialCacheStats, initialThumbnailStats, users, initialTab, initialProwlarr, initialQbittorrent, initialTorrentImport, initialTelegram, initialAnilist, initialKomga, initialMetadataProviders, initialStatusMappings, initialSeriesStatuses, initialProviderStatuses }: SettingsPageProps) {
const { t, locale, setLocale } = useTranslation(); const { t, locale, setLocale } = useTranslation();
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@@ -521,28 +531,28 @@ export default function SettingsPage({ initialSettings, initialCacheStats, initi
{activeTab === "metadata" && (<> {activeTab === "metadata" && (<>
{/* Metadata Providers */} {/* Metadata Providers */}
<MetadataProvidersCard handleUpdateSetting={handleUpdateSetting} /> <MetadataProvidersCard handleUpdateSetting={handleUpdateSetting} initialData={initialMetadataProviders} />
{/* Status Mappings */} {/* Status Mappings */}
<StatusMappingsCard /> <StatusMappingsCard initialStatusMappings={initialStatusMappings} initialSeriesStatuses={initialSeriesStatuses} initialProviderStatuses={initialProviderStatuses} />
</>)} </>)}
{activeTab === "downloadTools" && (<> {activeTab === "downloadTools" && (<>
{/* Prowlarr */} {/* Prowlarr */}
<ProwlarrCard handleUpdateSetting={handleUpdateSetting} /> <ProwlarrCard handleUpdateSetting={handleUpdateSetting} initialData={initialProwlarr} />
{/* qBittorrent */} {/* qBittorrent */}
<QBittorrentCard handleUpdateSetting={handleUpdateSetting} /> <QBittorrentCard handleUpdateSetting={handleUpdateSetting} initialQbittorrent={initialQbittorrent} initialTorrentImport={initialTorrentImport} />
</>)} </>)}
{activeTab === "notifications" && (<> {activeTab === "notifications" && (<>
{/* Telegram Notifications */} {/* Telegram Notifications */}
<TelegramCard handleUpdateSetting={handleUpdateSetting} /> <TelegramCard handleUpdateSetting={handleUpdateSetting} initialData={initialTelegram} />
</>)} </>)}
{activeTab === "readingStatus" && (<> {activeTab === "readingStatus" && (<>
<AnilistTab handleUpdateSetting={handleUpdateSetting} users={users} /> <AnilistTab handleUpdateSetting={handleUpdateSetting} users={users} initialData={initialAnilist} />
<KomgaSyncCard users={users} /> <KomgaSyncCard users={users} initialData={initialKomga} />
</>)} </>)}
<Toaster /> <Toaster />

View File

@@ -8,19 +8,21 @@ import { useTranslation } from "@/lib/i18n/context";
export function AnilistTab({ export function AnilistTab({
handleUpdateSetting, handleUpdateSetting,
users, users,
initialData,
}: { }: {
handleUpdateSetting: (key: string, value: unknown) => Promise<void>; handleUpdateSetting: (key: string, value: unknown) => Promise<void>;
users: UserDto[]; users: UserDto[];
initialData: Record<string, unknown> | null;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [origin, setOrigin] = useState(""); const [origin, setOrigin] = useState("");
useEffect(() => { setOrigin(window.location.origin); }, []); useEffect(() => { setOrigin(window.location.origin); }, []);
const [clientId, setClientId] = useState(""); const [clientId, setClientId] = useState(initialData?.client_id ? String(initialData.client_id) : "");
const [token, setToken] = useState(""); const [token, setToken] = useState(initialData?.access_token ? String(initialData.access_token) : "");
const [userId, setUserId] = useState(""); const [userId, setUserId] = useState(initialData?.user_id ? String(initialData.user_id) : "");
const [localUserId, setLocalUserId] = useState(""); const [localUserId, setLocalUserId] = useState(initialData?.local_user_id ? String(initialData.local_user_id) : "");
const [isTesting, setIsTesting] = useState(false); const [isTesting, setIsTesting] = useState(false);
const [viewer, setViewer] = useState<AnilistStatusDto | null>(null); const [viewer, setViewer] = useState<AnilistStatusDto | null>(null);
const [testError, setTestError] = useState<string | null>(null); const [testError, setTestError] = useState<string | null>(null);
@@ -33,21 +35,6 @@ export function AnilistTab({
const [isPreviewing, setIsPreviewing] = useState(false); const [isPreviewing, setIsPreviewing] = useState(false);
const [previewItems, setPreviewItems] = useState<AnilistSyncPreviewItemDto[] | null>(null); const [previewItems, setPreviewItems] = useState<AnilistSyncPreviewItemDto[] | null>(null);
useEffect(() => {
fetch("/api/settings/anilist")
.then((r) => r.ok ? r.json() : null)
.then((data) => {
if (data) {
if (data.client_id) setClientId(String(data.client_id));
if (data.access_token) setToken(data.access_token);
if (data.user_id) setUserId(String(data.user_id));
if (data.local_user_id) setLocalUserId(String(data.local_user_id));
}
})
.catch(() => {});
}, []);
function buildAnilistSettings() { function buildAnilistSettings() {
return { return {
client_id: clientId || undefined, client_id: clientId || undefined,

View File

@@ -5,12 +5,12 @@ import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, Form
import { KomgaSyncResponse, KomgaSyncReportSummary, UserDto } from "@/lib/api"; import { KomgaSyncResponse, KomgaSyncReportSummary, UserDto } from "@/lib/api";
import { useTranslation } from "@/lib/i18n/context"; import { useTranslation } from "@/lib/i18n/context";
export function KomgaSyncCard({ users }: { users: UserDto[] }) { export function KomgaSyncCard({ users, initialData }: { users: UserDto[]; initialData: Record<string, unknown> | null }) {
const { t, locale } = useTranslation(); const { t, locale } = useTranslation();
const [komgaUrl, setKomgaUrl] = useState(""); const [komgaUrl, setKomgaUrl] = useState(initialData?.url ? String(initialData.url) : "");
const [komgaUsername, setKomgaUsername] = useState(""); const [komgaUsername, setKomgaUsername] = useState(initialData?.username ? String(initialData.username) : "");
const [komgaPassword, setKomgaPassword] = useState(""); const [komgaPassword, setKomgaPassword] = useState("");
const [komgaUserId, setKomgaUserId] = useState(users[0]?.id ?? ""); const [komgaUserId, setKomgaUserId] = useState(initialData?.user_id ? String(initialData.user_id) : (users[0]?.id ?? ""));
const [isSyncing, setIsSyncing] = useState(false); const [isSyncing, setIsSyncing] = useState(false);
const [syncResult, setSyncResult] = useState<KomgaSyncResponse | null>(null); const [syncResult, setSyncResult] = useState<KomgaSyncResponse | null>(null);
const [syncError, setSyncError] = useState<string | null>(null); const [syncError, setSyncError] = useState<string | null>(null);
@@ -39,13 +39,6 @@ export function KomgaSyncCard({ users }: { users: UserDto[] }) {
useEffect(() => { useEffect(() => {
fetchReports(); fetchReports();
fetch("/api/settings/komga").then(r => r.ok ? r.json() : null).then(data => {
if (data) {
if (data.url) setKomgaUrl(data.url);
if (data.username) setKomgaUsername(data.username);
if (data.user_id) setKomgaUserId(data.user_id);
}
}).catch(() => {});
}, [fetchReports]); }, [fetchReports]);
async function handleViewReport(id: string) { async function handleViewReport(id: string) {

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState } from "react";
import { Card, CardHeader, CardTitle, CardDescription, CardContent, FormField, FormInput, FormSelect, Icon } from "@/app/components/ui"; import { Card, CardHeader, CardTitle, CardDescription, CardContent, FormField, FormInput, FormSelect, Icon } from "@/app/components/ui";
import { ProviderIcon } from "@/app/components/ProviderIcon"; import { ProviderIcon } from "@/app/components/ProviderIcon";
import { useTranslation } from "@/lib/i18n/context"; import { useTranslation } from "@/lib/i18n/context";
@@ -11,25 +11,22 @@ export const METADATA_LANGUAGES = [
{ value: "es", label: "Español" }, { value: "es", label: "Español" },
] as const; ] as const;
export function MetadataProvidersCard({ handleUpdateSetting }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void> }) { function extractInitialApiKeys(data: Record<string, unknown> | null): Record<string, string> {
const { t } = useTranslation(); const keys: Record<string, string> = {};
const [defaultProvider, setDefaultProvider] = useState("google_books"); if (data) {
const [metadataLanguage, setMetadataLanguage] = useState("en"); const comicvine = data.comicvine as Record<string, unknown> | undefined;
const [apiKeys, setApiKeys] = useState<Record<string, string>>({}); const googleBooks = data.google_books as Record<string, unknown> | undefined;
if (comicvine?.api_key) keys.comicvine = String(comicvine.api_key);
if (googleBooks?.api_key) keys.google_books = String(googleBooks.api_key);
}
return keys;
}
useEffect(() => { export function MetadataProvidersCard({ handleUpdateSetting, initialData }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void>; initialData: Record<string, unknown> | null }) {
fetch("/api/settings/metadata_providers") const { t } = useTranslation();
.then((r) => (r.ok ? r.json() : null)) const [defaultProvider, setDefaultProvider] = useState(initialData?.default_provider ? String(initialData.default_provider) : "google_books");
.then((data) => { const [metadataLanguage, setMetadataLanguage] = useState(initialData?.metadata_language ? String(initialData.metadata_language) : "en");
if (data) { const [apiKeys, setApiKeys] = useState<Record<string, string>>(extractInitialApiKeys(initialData));
if (data.default_provider) setDefaultProvider(data.default_provider);
if (data.metadata_language) setMetadataLanguage(data.metadata_language);
if (data.comicvine?.api_key) setApiKeys((prev) => ({ ...prev, comicvine: data.comicvine.api_key }));
if (data.google_books?.api_key) setApiKeys((prev) => ({ ...prev, google_books: data.google_books.api_key }));
}
})
.catch(() => {});
}, []);
function save(provider: string, lang: string, keys: Record<string, string>) { function save(provider: string, lang: string, keys: Record<string, string>) {
const value: Record<string, unknown> = { const value: Record<string, unknown> = {

View File

@@ -1,30 +1,19 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState } from "react";
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, Icon } from "@/app/components/ui"; import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, Icon } from "@/app/components/ui";
import { useTranslation } from "@/lib/i18n/context"; import { useTranslation } from "@/lib/i18n/context";
export function ProwlarrCard({ handleUpdateSetting }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void> }) { export function ProwlarrCard({ handleUpdateSetting, initialData }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void>; initialData: Record<string, unknown> | null }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [prowlarrUrl, setProwlarrUrl] = useState(""); const [prowlarrUrl, setProwlarrUrl] = useState(initialData?.url ? String(initialData.url) : "");
const [prowlarrApiKey, setProwlarrApiKey] = useState(""); const [prowlarrApiKey, setProwlarrApiKey] = useState(initialData?.api_key ? String(initialData.api_key) : "");
const [prowlarrCategories, setProwlarrCategories] = useState("7030, 7020"); const [prowlarrCategories, setProwlarrCategories] = useState(
Array.isArray(initialData?.categories) ? (initialData.categories as number[]).join(", ") : "7030, 7020"
);
const [isTesting, setIsTesting] = useState(false); const [isTesting, setIsTesting] = useState(false);
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null); const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
useEffect(() => {
fetch("/api/settings/prowlarr")
.then((r) => (r.ok ? r.json() : null))
.then((data) => {
if (data) {
if (data.url) setProwlarrUrl(data.url);
if (data.api_key) setProwlarrApiKey(data.api_key);
if (data.categories) setProwlarrCategories(data.categories.join(", "));
}
})
.catch(() => {});
}, []);
function saveProwlarr(url?: string, apiKey?: string, cats?: string) { function saveProwlarr(url?: string, apiKey?: string, cats?: string) {
const categories = (cats ?? prowlarrCategories) const categories = (cats ?? prowlarrCategories)
.split(",") .split(",")

View File

@@ -1,34 +1,17 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState } from "react";
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, FormSelect, Icon } from "@/app/components/ui"; import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, FormSelect, Icon } from "@/app/components/ui";
import { useTranslation } from "@/lib/i18n/context"; import { useTranslation } from "@/lib/i18n/context";
export function QBittorrentCard({ handleUpdateSetting }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void> }) { export function QBittorrentCard({ handleUpdateSetting, initialQbittorrent, initialTorrentImport }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void>; initialQbittorrent: Record<string, unknown> | null; initialTorrentImport: Record<string, unknown> | null }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [qbUrl, setQbUrl] = useState(""); const [qbUrl, setQbUrl] = useState(initialQbittorrent?.url ? String(initialQbittorrent.url) : "");
const [qbUsername, setQbUsername] = useState(""); const [qbUsername, setQbUsername] = useState(initialQbittorrent?.username ? String(initialQbittorrent.username) : "");
const [qbPassword, setQbPassword] = useState(""); const [qbPassword, setQbPassword] = useState(initialQbittorrent?.password ? String(initialQbittorrent.password) : "");
const [isTesting, setIsTesting] = useState(false); const [isTesting, setIsTesting] = useState(false);
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null); const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
const [importEnabled, setImportEnabled] = useState(false); const [importEnabled, setImportEnabled] = useState(initialTorrentImport?.enabled === true);
useEffect(() => {
fetch("/api/settings/qbittorrent")
.then((r) => (r.ok ? r.json() : null))
.then((data) => {
if (data) {
if (data.url) setQbUrl(data.url);
if (data.username) setQbUsername(data.username);
if (data.password) setQbPassword(data.password);
}
})
.catch(() => {});
fetch("/api/settings/torrent_import")
.then((r) => (r.ok ? r.json() : null))
.then((data) => { if (data?.enabled !== undefined) setImportEnabled(data.enabled); })
.catch(() => {});
}, []);
function saveQbittorrent() { function saveQbittorrent() {
handleUpdateSetting("qbittorrent", { handleUpdateSetting("qbittorrent", {

View File

@@ -1,36 +1,16 @@
"use client"; "use client";
import { useState, useEffect, useCallback, useMemo } from "react"; import { useState, useMemo } from "react";
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, FormSelect, Icon } from "@/app/components/ui"; import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, FormSelect, Icon } from "@/app/components/ui";
import { StatusMappingDto } from "@/lib/api"; import { StatusMappingDto } from "@/lib/api";
import { useTranslation } from "@/lib/i18n/context"; import { useTranslation } from "@/lib/i18n/context";
export function StatusMappingsCard() { export function StatusMappingsCard({ initialStatusMappings, initialSeriesStatuses, initialProviderStatuses }: { initialStatusMappings: Record<string, unknown>[]; initialSeriesStatuses: string[]; initialProviderStatuses: string[] }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [mappings, setMappings] = useState<StatusMappingDto[]>([]); const [mappings, setMappings] = useState<StatusMappingDto[]>(initialStatusMappings as unknown as StatusMappingDto[]);
const [targetStatuses, setTargetStatuses] = useState<string[]>([]); const [targetStatuses, setTargetStatuses] = useState<string[]>(initialSeriesStatuses);
const [providerStatuses, setProviderStatuses] = useState<string[]>([]); const [providerStatuses] = useState<string[]>(initialProviderStatuses);
const [newTargetName, setNewTargetName] = useState(""); const [newTargetName, setNewTargetName] = useState("");
const [loading, setLoading] = useState(true);
const loadData = useCallback(async () => {
try {
const [mRes, sRes, pRes] = await Promise.all([
fetch("/api/settings/status-mappings").then((r) => r.ok ? r.json() : []),
fetch("/api/series/statuses").then((r) => r.ok ? r.json() : []),
fetch("/api/series/provider-statuses").then((r) => r.ok ? r.json() : []),
]);
setMappings(mRes);
setTargetStatuses(sRes);
setProviderStatuses(pRes);
} catch {
// ignore
} finally {
setLoading(false);
}
}, []);
useEffect(() => { loadData(); }, [loadData]);
// Group mappings by target status (only those with a non-null mapped_status) // Group mappings by target status (only those with a non-null mapped_status)
const grouped = useMemo(() => { const grouped = useMemo(() => {
@@ -108,14 +88,6 @@ export function StatusMappingsCard() {
return translated !== key ? translated : status; return translated !== key ? translated : status;
} }
if (loading) {
return (
<Card className="mb-6">
<CardContent><p className="text-muted-foreground py-4">{t("common.loading")}</p></CardContent>
</Card>
);
}
return ( return (
<Card className="mb-6"> <Card className="mb-6">
<CardHeader> <CardHeader>

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState } from "react";
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, Icon } from "@/app/components/ui"; import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, FormField, FormInput, Icon } from "@/app/components/ui";
import { useTranslation } from "@/lib/i18n/context"; import { useTranslation } from "@/lib/i18n/context";
@@ -25,30 +25,18 @@ export const DEFAULT_EVENTS = {
download_detection_failed: true, download_detection_failed: true,
}; };
export function TelegramCard({ handleUpdateSetting }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void> }) { export function TelegramCard({ handleUpdateSetting, initialData }: { handleUpdateSetting: (key: string, value: unknown) => Promise<void>; initialData: Record<string, unknown> | null }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [botToken, setBotToken] = useState(""); const [botToken, setBotToken] = useState(initialData?.bot_token ? String(initialData.bot_token) : "");
const [chatId, setChatId] = useState(""); const [chatId, setChatId] = useState(initialData?.chat_id ? String(initialData.chat_id) : "");
const [enabled, setEnabled] = useState(false); const [enabled, setEnabled] = useState(initialData?.enabled === true);
const [events, setEvents] = useState(DEFAULT_EVENTS); const [events, setEvents] = useState(
initialData?.events ? { ...DEFAULT_EVENTS, ...(initialData.events as Record<string, boolean>) } : DEFAULT_EVENTS
);
const [isTesting, setIsTesting] = useState(false); const [isTesting, setIsTesting] = useState(false);
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null); const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
const [showHelp, setShowHelp] = useState(false); const [showHelp, setShowHelp] = useState(false);
useEffect(() => {
fetch("/api/settings/telegram")
.then((r) => (r.ok ? r.json() : null))
.then((data) => {
if (data) {
if (data.bot_token) setBotToken(data.bot_token);
if (data.chat_id) setChatId(data.chat_id);
if (data.enabled !== undefined) setEnabled(data.enabled);
if (data.events) setEvents({ ...DEFAULT_EVENTS, ...data.events });
}
})
.catch(() => {});
}, []);
function saveTelegram(token?: string, chat?: string, en?: boolean, ev?: typeof events) { function saveTelegram(token?: string, chat?: string, en?: boolean, ev?: typeof events) {
handleUpdateSetting("telegram", { handleUpdateSetting("telegram", {
bot_token: token ?? botToken, bot_token: token ?? botToken,

View File

@@ -1,30 +1,49 @@
import { getSettings, getCacheStats, getThumbnailStats, fetchUsers } from "@/lib/api"; import { getSettings, getCacheStats, getThumbnailStats, fetchUsers, apiFetch } from "@/lib/api";
import SettingsPage from "./SettingsPage"; import SettingsPage from "./SettingsPage";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export default async function SettingsPageWrapper({ searchParams }: { searchParams: Promise<{ tab?: string }> }) { export default async function SettingsPageWrapper({ searchParams }: { searchParams: Promise<{ tab?: string }> }) {
const { tab } = await searchParams; const { tab } = await searchParams;
const settings = await getSettings().catch(() => ({ const [settings, cacheStats, thumbnailStats, users, prowlarr, qbittorrent, torrentImport, telegram, anilist, komga, metadataProviders, statusMappings, seriesStatuses, providerStatuses] = await Promise.all([
image_processing: { format: "webp", quality: 85, filter: "lanczos3", max_width: 2160 }, getSettings().catch(() => ({
cache: { enabled: true, directory: "/tmp/stripstream-image-cache", max_size_mb: 10000 }, image_processing: { format: "webp", quality: 85, filter: "lanczos3", max_width: 2160 },
limits: { concurrent_renders: 4, timeout_seconds: 12, rate_limit_per_second: 120 }, cache: { enabled: true, directory: "/tmp/stripstream-image-cache", max_size_mb: 10000 },
thumbnail: { enabled: true, width: 300, height: 400, quality: 80, format: "webp", directory: "/data/thumbnails" } limits: { concurrent_renders: 4, timeout_seconds: 12, rate_limit_per_second: 120 },
})); thumbnail: { enabled: true, width: 300, height: 400, quality: 80, format: "webp", directory: "/data/thumbnails" }
})),
getCacheStats().catch(() => ({ total_size_mb: 0, file_count: 0, directory: "/tmp/stripstream-image-cache" })),
getThumbnailStats().catch(() => ({ total_size_mb: 0, file_count: 0, directory: "/data/thumbnails" })),
fetchUsers().catch(() => []),
apiFetch<Record<string, unknown>>("/settings/prowlarr").catch(() => null),
apiFetch<Record<string, unknown>>("/settings/qbittorrent").catch(() => null),
apiFetch<Record<string, unknown>>("/settings/torrent_import").catch(() => null),
apiFetch<Record<string, unknown>>("/settings/telegram").catch(() => null),
apiFetch<Record<string, unknown>>("/settings/anilist").catch(() => null),
apiFetch<Record<string, unknown>>("/settings/komga").catch(() => null),
apiFetch<Record<string, unknown>>("/settings/metadata_providers").catch(() => null),
apiFetch<unknown[]>("/settings/status-mappings").catch(() => []),
apiFetch<unknown[]>("/series/statuses").catch(() => []),
apiFetch<unknown[]>("/series/provider-statuses").catch(() => []),
]);
const cacheStats = await getCacheStats().catch(() => ({ return (
total_size_mb: 0, <SettingsPage
file_count: 0, initialSettings={settings}
directory: "/tmp/stripstream-image-cache" initialCacheStats={cacheStats}
})); initialThumbnailStats={thumbnailStats}
users={users}
const thumbnailStats = await getThumbnailStats().catch(() => ({ initialTab={tab}
total_size_mb: 0, initialProwlarr={prowlarr}
file_count: 0, initialQbittorrent={qbittorrent}
directory: "/data/thumbnails" initialTorrentImport={torrentImport}
})); initialTelegram={telegram}
initialAnilist={anilist}
const users = await fetchUsers().catch(() => []); initialKomga={komga}
initialMetadataProviders={metadataProviders}
return <SettingsPage initialSettings={settings} initialCacheStats={cacheStats} initialThumbnailStats={thumbnailStats} users={users} initialTab={tab} />; initialStatusMappings={statusMappings as Record<string, unknown>[]}
initialSeriesStatuses={seriesStatuses as string[]}
initialProviderStatuses={providerStatuses as string[]}
/>
);
} }