diff --git a/index.html b/index.html index ec0ec58..1f32b8a 100644 --- a/index.html +++ b/index.html @@ -67,8 +67,10 @@ + + diff --git a/src/App.tsx b/src/App.tsx index ce076c9..2160bca 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ import ErrorBoundary from "./components/ErrorBoundary"; import Footer from "./components/Footer"; import Header, { RefreshToast } from "./components/Header"; import { useBackground } from "./hooks/use-background"; +import { useTheme } from "./hooks/use-theme"; import { InjectContext } from "./lib/inject"; import { fetchSetting } from "./lib/nezha-api"; import { cn } from "./lib/utils"; @@ -39,6 +40,7 @@ const MainApp: React.FC = () => { retry: false, }); const { i18n } = useTranslation(); + const { setTheme } = useTheme(); const [isCustomCodeInjected, setIsCustomCodeInjected] = useState(false); const { backgroundImage: customBackgroundImage } = useBackground(); @@ -55,7 +57,6 @@ const MainApp: React.FC = () => { setIsCustomCodeInjected(true); } - // 同步自定义配置到全局变量 if (config.custom_logo) window.CustomLogo = config.custom_logo; if (config.custom_description) @@ -79,6 +80,16 @@ const MainApp: React.FC = () => { return () => clearInterval(interval); }, [settingData]); + // 检测是否强制指定了主题颜色 + const forceTheme = + (window.ForceTheme as string) !== "" ? window.ForceTheme : undefined; + + useEffect(() => { + if (forceTheme === "dark" || forceTheme === "light") { + setTheme(forceTheme); + } + }, [forceTheme, setTheme]); + const initialBackendError = !settingData ? toError(error) : null; if (settingData?.data?.config?.custom_code && !isCustomCodeInjected) { @@ -108,10 +119,10 @@ const MainApp: React.FC = () => { "hidden sm:block": customMobileBackgroundImage, }, )} - style={{ + style={{ backgroundImage: `url(${customBackgroundImage})`, - backfaceVisibility: 'hidden', - perspective: '1000px' + backfaceVisibility: "hidden", + perspective: "1000px", }} /> )} @@ -120,10 +131,10 @@ const MainApp: React.FC = () => { className={cn( "fixed inset-0 z-0 bg-cover w-screen h-screen bg-no-repeat bg-center transition-none sm:hidden dark:brightness-75", )} - style={{ + style={{ backgroundImage: `url(${customMobileBackgroundImage})`, - backfaceVisibility: 'hidden', - perspective: '1000px' + backfaceVisibility: "hidden", + perspective: "1000px", }} /> )} diff --git a/src/api/domain.ts b/src/api/domain.ts index b50b7ed..b44d064 100644 --- a/src/api/domain.ts +++ b/src/api/domain.ts @@ -1,14 +1,14 @@ // 这是一个临时的类型定义,为了让代码能顺利编译。 // 理想情况下,它应该从由 swagger 生成的 types/api.ts 文件中导入。 export interface Domain { - ID: number; - Domain: string; - Status: 'verified' | 'pending' | 'expired'; - VerifyToken: string; - BillingData: any; - CreatedAt: string; - UpdatedAt: string; - expires_in_days?: number; + ID: number; + Domain: string; + Status: "verified" | "pending" | "expired"; + VerifyToken: string; + BillingData: any; + CreatedAt: string; + UpdatedAt: string; + expires_in_days?: number; } /** @@ -16,26 +16,25 @@ export interface Domain { * TanStack Query 将会调用它。 */ export const getDomains = async (): Promise => { - const response = await fetch('/api/v1/domains?scope=public'); + const response = await fetch("/api/v1/domains?scope=public"); - if (!response.ok) { - throw new Error('网络响应错误'); - } + if (!response.ok) { + throw new Error("网络响应错误"); + } - // 后端返回的数据结构是 { success: true, data: [...] } 或类似结构 - const result = await response.json(); + // 后端返回的数据结构是 { success: true, data: [...] } 或类似结构 + const result = await response.json(); - // 根据 admin-frontend 的经验,数据可能在 result.data.data 中 - // 但在这里我们先假设数据直接在 result.data 中 - if (result && result.data) { - return result.data; - } - - // 如果直接返回的就是数组 - if (Array.isArray(result)) { - return result; - } + // 根据 admin-frontend 的经验,数据可能在 result.data.data 中 + // 但在这里我们先假设数据直接在 result.data 中 + if (result?.data) { + return result.data; + } - throw new Error('返回的数据格式不正确'); + // 如果直接返回的就是数组 + if (Array.isArray(result)) { + return result; + } + + throw new Error("返回的数据格式不正确"); }; - diff --git a/src/components/DomainStatus.tsx b/src/components/DomainStatus.tsx index 923fe2d..d4717a1 100644 --- a/src/components/DomainStatus.tsx +++ b/src/components/DomainStatus.tsx @@ -1,183 +1,248 @@ -import { useQuery } from '@tanstack/react-query'; -import { getDomains, Domain } from '@/api/domain'; -import { CalendarDays, DollarSign } from 'lucide-react'; -import { cn } from '@/lib/utils'; -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import { useQuery } from "@tanstack/react-query"; +import { CalendarDays, DollarSign } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { type Domain, getDomains } from "@/api/domain"; +import { cn } from "@/lib/utils"; import RemainPercentBar from "./RemainPercentBar"; const DomainNoteTags = ({ notes }: { notes?: string }) => { - if (!notes) { - return null; - } + if (!notes) { + return null; + } - const colors = [ - 'bg-blue-500 text-white', - 'bg-green-500 text-white', - 'bg-purple-500 text-white', - 'bg-red-500 text-white', - 'bg-gray-600 text-white', - ]; + const colors = [ + "bg-blue-500 text-white", + "bg-green-500 text-white", + "bg-purple-500 text-white", + "bg-red-500 text-white", + "bg-gray-600 text-white", + ]; - const tags = notes.split(';').map(tag => tag.trim()).filter(tag => tag); + const tags = notes + .split(";") + .map((tag) => tag.trim()) + .filter((tag) => tag); - return ( -
- {tags.map((tag, index) => ( - - {tag} - - ))} -
- ); + return ( +
+ {tags.map((tag, index) => ( + + {tag} + + ))} +
+ ); }; const DomainCardInline = ({ domain }: { domain: Domain }) => { - const { t } = useTranslation(); - const expiresIn = domain.expires_in_days; - const billingData = domain.BillingData || {}; - const customBackgroundImage = (window as any).CustomBackgroundImage !== "" ? (window as any).CustomBackgroundImage : undefined; + const { t } = useTranslation(); + const expiresIn = domain.expires_in_days; + const billingData = domain.BillingData || {}; + const customBackgroundImage = + (window as any).CustomBackgroundImage !== "" + ? (window as any).CustomBackgroundImage + : undefined; - let statusColorClass = 'bg-green-500'; - if (expiresIn !== undefined && expiresIn <= 10) statusColorClass = 'bg-red-500'; - else if (expiresIn !== undefined && expiresIn <= 30) statusColorClass = 'bg-yellow-500'; + let statusColorClass = "bg-green-500"; + if (expiresIn !== undefined && expiresIn <= 10) + statusColorClass = "bg-red-500"; + else if (expiresIn !== undefined && expiresIn <= 30) + statusColorClass = "bg-yellow-500"; - return ( - -
-
-
- - - - -

{domain.Domain}

-
-
- {billingData.registrar || 'N/A'} -
- - {t('domain.expiryPrefix')}: {billingData.endDate ? new Date(billingData.endDate).toLocaleDateString() : 'N/A'} -
-
- - {billingData.renewalPrice || 'N/A'} -
- {expiresIn !== undefined ? `${expiresIn} ${t('domain.days')}` : 'N/A'} -
-
- -
-
- ); + return ( + +
+
+
+ + + + +

{domain.Domain}

+
+
+ + {billingData.registrar || "N/A"} + +
+ + + {t("domain.expiryPrefix")}:{" "} + {billingData.endDate + ? new Date(billingData.endDate).toLocaleDateString() + : "N/A"} + +
+
+ + {billingData.renewalPrice || "N/A"} +
+ + {expiresIn !== undefined + ? `${expiresIn} ${t("domain.days")}` + : "N/A"} + +
+
+ +
+
+ ); }; const DomainCard = ({ domain }: { domain: Domain }) => { - const { t } = useTranslation(); - const expiresIn = domain.expires_in_days; - const billingData = domain.BillingData || {}; - const customBackgroundImage = (window as any).CustomBackgroundImage !== "" ? (window as any).CustomBackgroundImage : undefined; + const { t } = useTranslation(); + const expiresIn = domain.expires_in_days; + const billingData = domain.BillingData || {}; + const customBackgroundImage = + (window as any).CustomBackgroundImage !== "" + ? (window as any).CustomBackgroundImage + : undefined; - return ( - -
-
-

{domain.Domain}

-
-
- {billingData.registrar || t('domain.unknownRegistrar')} -
- - {billingData.endDate ? new Date(billingData.endDate).toLocaleDateString() : 'N/A'} -
-
-
-
- - {billingData.renewalPrice || 'N/A'} -
-
- -
- {expiresIn !== undefined ? `${expiresIn}${t('domain.days')}` : 'N/A'} -
-
- -
-
-
- ); + return ( + +
+
+

+ {domain.Domain} +

+
+
+ + {billingData.registrar || t("domain.unknownRegistrar")} + +
+ + + {billingData.endDate + ? new Date(billingData.endDate).toLocaleDateString() + : "N/A"} + +
+
+
+
+ + + {billingData.renewalPrice || "N/A"} + +
+
+ +
+ + {expiresIn !== undefined + ? `${expiresIn}${t("domain.days")}` + : "N/A"} + +
+
+ +
+
+
+ ); }; export const DomainStatus = () => { - const { data: domains, isLoading, error } = useQuery({ - queryKey: ['domains'], - queryFn: getDomains, - refetchInterval: 60 * 60 * 1000, - }); - - const [inline, setInline] = useState("0"); - useEffect(() => { - const checkInlineSettings = () => { - const isMobile = window.innerWidth < 768; - if (!isMobile) { - const inlineState = localStorage.getItem("inline"); - if ((window as any).ForceCardInline) setInline("1"); - else if (inlineState !== null) setInline(inlineState); - } - }; - checkInlineSettings(); - - const handleStorageChange = () => checkInlineSettings(); - window.addEventListener('storage', handleStorageChange); - - const handleViewChange = () => { - const inlineState = localStorage.getItem("inline"); - setInline(inlineState ?? "0"); - }; - window.addEventListener('nezha-view-change', handleViewChange); + const { + data: domains, + isLoading, + error, + } = useQuery({ + queryKey: ["domains"], + queryFn: getDomains, + refetchInterval: 60 * 60 * 1000, + }); - return () => { - window.removeEventListener('storage', handleStorageChange); - window.removeEventListener('nezha-view-change', handleViewChange); - }; - }, []); + const [inline, setInline] = useState("0"); + useEffect(() => { + const checkInlineSettings = () => { + const isMobile = window.innerWidth < 768; + if (!isMobile) { + const inlineState = localStorage.getItem("inline"); + if ((window as any).ForceCardInline) setInline("1"); + else if (inlineState !== null) setInline(inlineState); + } + }; + checkInlineSettings(); - const filteredDomains = domains?.filter(d => d.Status === 'verified' || d.Status === 'expired'); + const handleStorageChange = () => checkInlineSettings(); + window.addEventListener("storage", handleStorageChange); - if (error || isLoading || !filteredDomains || filteredDomains.length === 0) { - return null; - } + const handleViewChange = () => { + const inlineState = localStorage.getItem("inline"); + setInline(inlineState ?? "0"); + }; + window.addEventListener("nezha-view-change", handleViewChange); - if (inline === '1') { - return ( -
- {filteredDomains.map(domain => ( - - ))} -
- ); - } + return () => { + window.removeEventListener("storage", handleStorageChange); + window.removeEventListener("nezha-view-change", handleViewChange); + }; + }, []); - return ( -
- {filteredDomains.map(domain => ( - - ))} -
- ); + const filteredDomains = domains?.filter( + (d) => d.Status === "verified" || d.Status === "expired", + ); + + if (error || isLoading || !filteredDomains || filteredDomains.length === 0) { + return null; + } + + if (inline === "1") { + return ( +
+ {filteredDomains.map((domain) => ( + + ))} +
+ ); + } + + return ( +
+ {filteredDomains.map((domain) => ( + + ))} +
+ ); }; diff --git a/src/components/ServerOverview.tsx b/src/components/ServerOverview.tsx index 2f8136f..43bdc62 100644 --- a/src/components/ServerOverview.tsx +++ b/src/components/ServerOverview.tsx @@ -2,15 +2,14 @@ import { ArrowDownCircleIcon, ArrowUpCircleIcon, } from "@heroicons/react/20/solid"; +import { Globe } from "lucide-react"; import { useTranslation } from "react-i18next"; import { Card, CardContent } from "@/components/ui/card"; import { useStatus } from "@/hooks/use-status"; import { formatBytes } from "@/lib/format"; import { cn } from "@/lib/utils"; -import { Globe } from "lucide-react"; import NumericText from "./NumericText"; - type ServerOverviewProps = { online: number; offline: number; @@ -20,8 +19,8 @@ type ServerOverviewProps = { upSpeed: number; downSpeed: number; totalDomains: number; // 新增:接收域名总数 - onViewChange: (view: 'servers' | 'domains') => void; // 新增:点击事件回调 - activeView: 'servers' | 'domains'; // 新增:当前激活的视图 + onViewChange: (view: "servers" | "domains") => void; // 新增:点击事件回调 + activeView: "servers" | "domains"; // 新增:当前激活的视图 }; export default function ServerOverview({ @@ -51,8 +50,10 @@ export default function ServerOverview({ : undefined; // 新增:一个组合了两个动作的点击处理函数 - const handleServerCardClick = (serverStatus: 'all' | 'online' | 'offline') => { - onViewChange('servers'); // 动作1: 确保视图切换回服务器 + const handleServerCardClick = ( + serverStatus: "all" | "online" | "offline", + ) => { + onViewChange("servers"); // 动作1: 确保视图切换回服务器 setStatus(serverStatus); // 动作2: 执行原有的状态筛选 }; @@ -127,7 +128,6 @@ export default function ServerOverview({ activeView === "servers" && status === "offline", }, )} - >
@@ -152,13 +152,16 @@ export default function ServerOverview({ "bg-card/70": customBackgroundImage, }, { - "ring-indigo-500 ring-2 border-transparent": activeView === "domains", + "ring-indigo-500 ring-2 border-transparent": + activeView === "domains", }, )} >
-

{t("serverOverview.totalDomains")}

+

+ {t("serverOverview.totalDomains")} +

{totalDomains}
diff --git a/src/components/ThemeProvider.tsx b/src/components/ThemeProvider.tsx index 360d7c3..d568bae 100644 --- a/src/components/ThemeProvider.tsx +++ b/src/components/ThemeProvider.tsx @@ -1,5 +1,5 @@ -import { createContext, type ReactNode, useEffect, useState } from "react"; import { DateTime } from "luxon"; +import { createContext, type ReactNode, useEffect, useState } from "react"; export type Theme = "dark" | "light" | "system" | "scheduled"; @@ -41,7 +41,8 @@ export function ThemeProvider({ }, 60000); const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); - const handler = (e: MediaQueryListEvent) => setIsSystemDark(e.matches); + const handler = (e?: MediaQueryListEvent) => + setIsSystemDark(e?.matches ?? mediaQuery.matches); mediaQuery.addEventListener("change", handler); return () => { @@ -54,7 +55,6 @@ export function ThemeProvider({ const root = window.document.documentElement; const updateThemeColor = (nextTheme: "light" | "dark") => { - const themeColor = nextTheme === "dark" ? "hsl(30 15% 8%)" : "hsl(0 0% 98%)"; document @@ -94,7 +94,6 @@ export function ThemeProvider({ }; }, [theme, hour, isSystemDark]); - const value = { theme, setTheme: (theme: Theme) => { diff --git a/src/components/ui/chart.tsx b/src/components/ui/chart.tsx index 3f7c26b..106f000 100644 --- a/src/components/ui/chart.tsx +++ b/src/components/ui/chart.tsx @@ -79,14 +79,19 @@ const ChartContainer = React.forwardRef< data-chart={chartId} ref={ref} className={cn( - "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden", + "flex aspect-video min-w-0 justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden", className, )} {...props} > {useResponsiveContainer ? ( - + {children} ) : ( diff --git a/src/index.css b/src/index.css index 62e4ace..9722add 100644 --- a/src/index.css +++ b/src/index.css @@ -201,10 +201,15 @@ /* font-feature-settings: "rlig" 1, "calt" 1; */ font-synthesis-weight: none; text-rendering: optimizeLegibility; - font-family: 'LXGW WenKai Screen', sans-serif; + font-family: "LXGW WenKai Screen", sans-serif; } - h1, h2, h3, h4, h5, h6 { - font-family: 'LXGW WenKai Screen', sans-serif; + h1, + h2, + h3, + h4, + h5, + h6 { + font-family: "LXGW WenKai Screen", sans-serif; } } @@ -438,19 +443,21 @@ /* Custom background overlays */ .bg-cover::after { - content: ''; + content: ""; position: absolute; inset: 0; pointer-events: none; - transition: backdrop-filter 0.3s ease, background 0.3s ease; + transition: + backdrop-filter 0.3s ease, + background 0.3s ease; } .dark .bg-cover::after { backdrop-filter: blur(8px); - background: rgba(0, 0, 0, .6); + background: rgba(0, 0, 0, 0.6); } .light .bg-cover::after { backdrop-filter: blur(0); - background: rgba(255, 255, 255, .3); + background: rgba(255, 255, 255, 0.3); } diff --git a/src/lib/custom-config.ts b/src/lib/custom-config.ts index b836867..63c42c3 100644 --- a/src/lib/custom-config.ts +++ b/src/lib/custom-config.ts @@ -1,38 +1,37 @@ import { DateTime } from "luxon"; export function initCustomConfig() { - try { - const hour = DateTime.now().hour; - const isNight = hour >= 18 || hour < 6; + try { + const hour = DateTime.now().hour; + const isNight = hour >= 18 || hour < 6; - // Use default values if window variables are not already set (e.g. by backend custom_code) - // although the goal is to hardcode these for "consistency". - - window.CustomBackgroundImage = isNight - ? 'https://loohui.com/wp-content/uploads/images/background.jpg' - : 'https://loohui.com/wp-content/uploads/images/background_day.jpg'; - - window.CustomMobileBackgroundImage = window.CustomBackgroundImage; - window.ForceTheme = isNight ? 'dark' : 'light'; + // Use default values if window variables are not already set (e.g. by backend custom_code) + // although the goal is to hardcode these for "consistency". - /* LOGO / 副标题 / 链接 */ - window.CustomLogo = 'https://loohui.com/wp-content/uploads/images/pet.png'; - window.CustomDesc = '树树皆秋色,山山唯落晖'; - window.CustomLinks = JSON.stringify([ - { "link": "https://loohui.com/", "name": "返回Blog", "blank": false } - ]); + window.CustomBackgroundImage = isNight + ? "https://loohui.com/wp-content/uploads/images/background.jpg" + : "https://loohui.com/wp-content/uploads/images/background_day.jpg"; - // Handle internal redirects if needed - document.addEventListener('click', (e: MouseEvent) => { - const target = e.target as HTMLElement; - const a = target.closest('a'); - if (a && a.href === 'https://loohui.com/') { - e.preventDefault(); - window.location.href = a.href; - } - }); + window.CustomMobileBackgroundImage = window.CustomBackgroundImage; + window.ForceTheme = isNight ? "dark" : "light"; - } catch (e) { - console.error('[Nezha custom_config] crash:', e); - } + /* LOGO / 副标题 / 链接 */ + window.CustomLogo = "https://loohui.com/wp-content/uploads/images/pet.png"; + window.CustomDesc = "树树皆秋色,山山唯落晖"; + window.CustomLinks = JSON.stringify([ + { link: "https://loohui.com/", name: "返回Blog", blank: false }, + ]); + + // Handle internal redirects if needed + document.addEventListener("click", (e: MouseEvent) => { + const target = e.target as HTMLElement; + const a = target.closest("a"); + if (a && a.href === "https://loohui.com/") { + e.preventDefault(); + window.location.href = a.href; + } + }); + } catch (e) { + console.error("[Nezha custom_config] crash:", e); + } } diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index cf5bd30..b31c1de 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -178,4 +178,3 @@ "all": "All" } } - diff --git a/src/locales/id/translation.json b/src/locales/id/translation.json index b7a6079..c7cfda6 100644 --- a/src/locales/id/translation.json +++ b/src/locales/id/translation.json @@ -1,171 +1,171 @@ { - "nezha": "Pemantauan Nezha", - "overview": "Ikhtisar", - "dashboard": "Dasbor", - "login": "Masuk", - "online": "Daring", - "offline": "Luring", - "whereTheTimeIs": "Di mana waktunya", - "refreshing": "Menyegarkan", - "info": { - "websocketConnecting": "WebSocket menghubungkan", - "processing": "Memproses...", - "websocketDisconnected": "WebSocket terputus", - "websocketConnected": "WebSocket terhubung", - "noServers": "Tidak ada server yang tersedia", - "noMatchingServers": "Tidak ada server yang cocok dengan filter saat ini" - }, - "serverOverview": { - "totalServers": "Total Server", - "onlineServers": "Server Daring", - "offlineServers": "Server Luring", - "speed": "Kecepatan", - "totalBandwidth": "Total Bandwidth", - "network": "Jaringan" - }, - "theme": { - "light": "Terang", - "dark": "Gelap", - "system": "Sistem" - }, - "map": { - "Distributions": "Server didistribusikan di", - "Regions": "Wilayah", - "Servers": "server" - }, - "serverCard": { - "mem": "MEM", - "stg": "STG", - "days": "Hari", - "hours": "Jam", - "upload": "Unggah", - "download": "Unduh", - "system": "Sistem", - "uptime": "Waktu aktif", - "totalUpload": "Unggah", - "totalDownload": "Unduh" - }, - "cycleTransfer": { - "used": "digunakan", - "total": "total", - "nextUpdate": "pembaruan berikutnya" - }, - "serviceTracker": { - "noService": "Tidak ada data layanan", - "uptime": "Waktu aktif", - "delay": "Tunda", - "daysAgo": "hari yang lalu", - "today": "Hari ini", - "loading": "Memuat..." - }, - "serverDetail": { - "status": "Status", - "online": "Daring", - "days": "Hari", - "hours": "Jam", - "offline": "Luring", - "unknown": "Tidak diketahui", - "uptime": "Waktu aktif", - "version": "Versi", - "arch": "Arsitektur", - "mem": "Memori", - "disk": "Disk", - "region": "Wilayah", - "system": "Sistem", - "upload": "Unggah", - "download": "Unduh", - "lastActive": "Waktu aktif terakhir", - "temperature": "Suhu", - "bootTime": "Waktu boot" - }, - "serverDetailChart": { - "process": "Proses", - "disk": "Disk", - "mem": "Memori", - "swap": "Swap", - "upload": "Unggah", - "download": "Unduh", - "realtime": "Waktu nyata", - "period1d": "1 Hari", - "period7d": "7 Hari", - "period30d": "30 Hari", - "tsdbRequired": "Aktifkan TSDB untuk menggunakan data historis", - "loginRequired": "Silakan masuk untuk melihat" - }, - "footer": { - "themeBy": "Tema oleh " - }, - "language": { - "zh-CN": "简体中文", - "zh-TW": "繁體中文", - "en-US": "Inggris", - "de-DE": "Jerman", - "es-ES": "Spanyol", - "ru-RU": "Rusia", - "ta-IN": "Tamil" - }, - "error": { - "pageNotFound": "Halaman tidak ditemukan", - "backToHome": "Kembali ke beranda", - "backendUnavailableTitle": "API backend tidak tersedia", - "backendUnavailableDescription": "Halaman masih tersedia, tetapi data tidak dapat dimuat saat ini." - }, - "tabSwitch": { - "Detail": "Detail", - "Network": "Jaringan" - }, - "monitor": { - "noData": "Tidak ada data pemantauan server, harap tambahkan pemantauan layanan terlebih dahulu", - "avgDelay": "Latensi", - "monitorCount": "Layanan", - "packetLoss": "Kehilangan Paket", - "clearSelections": "Bersihkan", - "peakCut": "Pemotongan puncak", - "loginRequired": "Silakan masuk untuk melihat", - "period1d": "1 Hari", - "period7d": "7 Hari", - "period30d": "30 Hari" - }, - "pwa": { - "offlineReady": "Aplikasi siap bekerja luring", - "newContent": "Konten baru tersedia", - "reload": "Perbarui" - }, - "billingInfo": { - "remaining": "Tersisa", - "error": "galat", - "indefinite": "Tak terbatas", - "expired": "Kedaluwarsa", - "days": "hari", - "price": "Harga", - "free": "Gratis", - "usage-baseed": "Berdasarkan penggunaan" - }, - "TypeCommand": "Ketik perintah atau cari...", - "NoResults": "Tidak ada hasil ditemukan.", - "Servers": "Server", - "Shortcuts": "Jalan pintas", - "ToggleLightMode": "Alihkan Mode Terang", - "ToggleDarkMode": "Alihkan Mode Gelap", - "ToggleSystemMode": "Alihkan Mode Sistem", - "Home": "Beranda", - "sort": { - "label": "Urutkan", - "types": { - "default": "Default", - "name": "Nama", - "uptime": "Waktu aktif (uptime)", - "system": "Sistem", - "cpu": "CPU", - "mem": "Mem", - "disk": "Disk", - "up": "Unggah", - "down": "Unduh", - "up_total": "Total Unggah", - "down_total": "Total Unduh" - } - }, - "group": { - "all": "Semua" - } + "nezha": "Pemantauan Nezha", + "overview": "Ikhtisar", + "dashboard": "Dasbor", + "login": "Masuk", + "online": "Daring", + "offline": "Luring", + "whereTheTimeIs": "Di mana waktunya", + "refreshing": "Menyegarkan", + "info": { + "websocketConnecting": "WebSocket menghubungkan", + "processing": "Memproses...", + "websocketDisconnected": "WebSocket terputus", + "websocketConnected": "WebSocket terhubung", + "noServers": "Tidak ada server yang tersedia", + "noMatchingServers": "Tidak ada server yang cocok dengan filter saat ini" + }, + "serverOverview": { + "totalServers": "Total Server", + "onlineServers": "Server Daring", + "offlineServers": "Server Luring", + "speed": "Kecepatan", + "totalBandwidth": "Total Bandwidth", + "network": "Jaringan" + }, + "theme": { + "light": "Terang", + "dark": "Gelap", + "system": "Sistem" + }, + "map": { + "Distributions": "Server didistribusikan di", + "Regions": "Wilayah", + "Servers": "server" + }, + "serverCard": { + "mem": "MEM", + "stg": "STG", + "days": "Hari", + "hours": "Jam", + "upload": "Unggah", + "download": "Unduh", + "system": "Sistem", + "uptime": "Waktu aktif", + "totalUpload": "Unggah", + "totalDownload": "Unduh" + }, + "cycleTransfer": { + "used": "digunakan", + "total": "total", + "nextUpdate": "pembaruan berikutnya" + }, + "serviceTracker": { + "noService": "Tidak ada data layanan", + "uptime": "Waktu aktif", + "delay": "Tunda", + "daysAgo": "hari yang lalu", + "today": "Hari ini", + "loading": "Memuat..." + }, + "serverDetail": { + "status": "Status", + "online": "Daring", + "days": "Hari", + "hours": "Jam", + "offline": "Luring", + "unknown": "Tidak diketahui", + "uptime": "Waktu aktif", + "version": "Versi", + "arch": "Arsitektur", + "mem": "Memori", + "disk": "Disk", + "region": "Wilayah", + "system": "Sistem", + "upload": "Unggah", + "download": "Unduh", + "lastActive": "Waktu aktif terakhir", + "temperature": "Suhu", + "bootTime": "Waktu boot" + }, + "serverDetailChart": { + "process": "Proses", + "disk": "Disk", + "mem": "Memori", + "swap": "Swap", + "upload": "Unggah", + "download": "Unduh", + "realtime": "Waktu nyata", + "period1d": "1 Hari", + "period7d": "7 Hari", + "period30d": "30 Hari", + "tsdbRequired": "Aktifkan TSDB untuk menggunakan data historis", + "loginRequired": "Silakan masuk untuk melihat" + }, + "footer": { + "themeBy": "Tema oleh " + }, + "language": { + "zh-CN": "简体中文", + "zh-TW": "繁體中文", + "en-US": "Inggris", + "de-DE": "Jerman", + "es-ES": "Spanyol", + "ru-RU": "Rusia", + "ta-IN": "Tamil" + }, + "error": { + "pageNotFound": "Halaman tidak ditemukan", + "backToHome": "Kembali ke beranda", + "backendUnavailableTitle": "API backend tidak tersedia", + "backendUnavailableDescription": "Halaman masih tersedia, tetapi data tidak dapat dimuat saat ini." + }, + "tabSwitch": { + "Detail": "Detail", + "Network": "Jaringan" + }, + "monitor": { + "noData": "Tidak ada data pemantauan server, harap tambahkan pemantauan layanan terlebih dahulu", + "avgDelay": "Latensi", + "monitorCount": "Layanan", + "packetLoss": "Kehilangan Paket", + "clearSelections": "Bersihkan", + "peakCut": "Pemotongan puncak", + "loginRequired": "Silakan masuk untuk melihat", + "period1d": "1 Hari", + "period7d": "7 Hari", + "period30d": "30 Hari" + }, + "pwa": { + "offlineReady": "Aplikasi siap bekerja luring", + "newContent": "Konten baru tersedia", + "reload": "Perbarui" + }, + "billingInfo": { + "remaining": "Tersisa", + "error": "galat", + "indefinite": "Tak terbatas", + "expired": "Kedaluwarsa", + "days": "hari", + "price": "Harga", + "free": "Gratis", + "usage-baseed": "Berdasarkan penggunaan" + }, + "TypeCommand": "Ketik perintah atau cari...", + "NoResults": "Tidak ada hasil ditemukan.", + "Servers": "Server", + "Shortcuts": "Jalan pintas", + "ToggleLightMode": "Alihkan Mode Terang", + "ToggleDarkMode": "Alihkan Mode Gelap", + "ToggleSystemMode": "Alihkan Mode Sistem", + "Home": "Beranda", + "sort": { + "label": "Urutkan", + "types": { + "default": "Default", + "name": "Nama", + "uptime": "Waktu aktif (uptime)", + "system": "Sistem", + "cpu": "CPU", + "mem": "Mem", + "disk": "Disk", + "up": "Unggah", + "down": "Unduh", + "up_total": "Total Unggah", + "down_total": "Total Unduh" + } + }, + "group": { + "all": "Semua" + } } diff --git a/src/locales/pt_BR/translation.json b/src/locales/pt_BR/translation.json index e53a8f7..9368fd5 100644 --- a/src/locales/pt_BR/translation.json +++ b/src/locales/pt_BR/translation.json @@ -1,167 +1,167 @@ { - "overview": "Visão geral", - "dashboard": "Painel de controle", - "whereTheTimeIs": "Onde está a hora", - "refreshing": "Atualizando", - "info": { - "websocketConnecting": "Conectando WebSocket", - "websocketConnected": "WebSocket conectado", - "websocketDisconnected": "WebSocket desconectado", - "processing": "Processando...", - "noServers": "Nenhum servidor disponível", - "noMatchingServers": "Nenhum servidor corresponde aos filtros atuais" - }, - "serverOverview": { - "totalServers": "Total de Servidores", - "onlineServers": "Servidores Conectados", - "offlineServers": "Servidores Desconectados", - "totalBandwidth": "Largura de Banda Total", - "speed": "Velocidade", - "network": "Rede" - }, - "map": { - "Distributions": "Servidores estão distribuídos em", - "Regions": "Regiões", - "Servers": "servidores" - }, - "serverCard": { - "mem": "MEM", - "stg": "STG", - "days": "Dias", - "hours": "Horas", - "upload": "Upload", - "download": "Download", - "system": "Sistema", - "uptime": "Disponibilidade", - "totalUpload": "Envio total", - "totalDownload": "Recebimento total" - }, - "nezha": "Nezha Monitoramento", - "login": "Login", - "online": "Online", - "offline": "Offline", - "cycleTransfer": { - "used": "Usado", - "total": "Total", - "nextUpdate": "Próxima atualização" - }, - "serviceTracker": { - "noService": "Sem dados do serviço", - "uptime": "Disponibilidade", - "delay": "Atraso", - "daysAgo": "Dias anteriores", - "today": "Hoje", - "loading": "Carregando..." - }, - "serverDetail": { - "status": "Status", - "online": "Online", - "days": "Dias", - "hours": "Horas", - "offline": "Offline", - "unknown": "Desconhecido", - "uptime": "Disponibilidade", - "version": "Versão", - "arch": "Arquitetura", - "mem": "Memória", - "disk": "Armazenamento", - "region": "Região", - "system": "Sistema", - "upload": "Envio", - "download": "Recebimento", - "lastActive": "Ultima atividade", - "temperature": "Temperatura", - "bootTime": "Tempo de inicio" - }, - "serverDetailChart": { - "process": "Processo", - "disk": "Armazenamento", - "mem": "Memória", - "swap": "Swap", - "upload": "Envio", - "download": "Recebimento", - "realtime": "Em tempo real", - "period1d": "1 Dia", - "period7d": "7 Dias", - "period30d": "30 Dias", - "tsdbRequired": "Habilite o TSDB para usar dados históricos", - "loginRequired": "Por favor entre para visualizar" - }, - "footer": { - "themeBy": "Tema por " - }, - "language": { - "zh-CN": "Chinês simplificado", - "zh-TW": "Chinês Tradicional", - "en-US": "Inglês", - "de-DE": "Alemão", - "es-ES": "Espanhol", - "ru-RU": "Russo", - "ta-IN": "Tamil Indiano" - }, - "theme": { - "light": "Claro", - "dark": "Escuro", - "system": "Sistema" - }, - "error": { - "pageNotFound": "Página não encontrada", - "backToHome": "Voltar ao inicio", - "backendUnavailableTitle": "API Backend indisponível", - "backendUnavailableDescription": "A página ainda está disponível, mas os dados não podem ser carregados agora." - }, - "tabSwitch": { - "Detail": "Detalhes", - "Network": "Rede" - }, - "monitor": { - "noData": "Sem servidores para monitorar. por favor adicione um primeiro monitor de serviço", - "avgDelay": "Latência", - "monitorCount": "Serviços", - "packetLoss": "Perda de pacotes", - "clearSelections": "Limpar", - "peakCut": "Corte de pico", - "loginRequired": "Por favor entre para visualizar", - "period1d": "1 Dia", - "period7d": "7 Dias", - "period30d": "30 Dias" - }, - "pwa": { - "offlineReady": "Aplicativo pronto para funcionar offline", - "newContent": "Novo conteúdo disponivel", - "reload": "Atualizar" - }, - "billingInfo": { - "remaining": "Restante", - "error": "Erro", - "indefinite": "Indefinido", - "expired": "Expirado", - "days": "dias", - "price": "Preço", - "free": "Grátis", - "usage-baseed": "Baseado no uso" - }, - "TypeCommand": "Digite um comando ou pesquisa...", - "NoResults": "Nenhum resultado encontrado.", - "Servers": "Servidores", - "Shortcuts": "Atalhos", - "ToggleLightMode": "Habilitar tema claro", - "ToggleDarkMode": "habilitar tema escuro", - "ToggleSystemMode": "Habilitar modo do sistema", - "Home": "Inicio", - "sort": { - "label": "Organizar", - "types": { - "default": "Padrão", - "name": "Nome", - "uptime": "Disponibilidade", - "system": "Sistema", - "cpu": "CPU", - "mem": "Mem", - "disk": "Disco" - } - }, - "group": { - "all": "Todos" - } + "overview": "Visão geral", + "dashboard": "Painel de controle", + "whereTheTimeIs": "Onde está a hora", + "refreshing": "Atualizando", + "info": { + "websocketConnecting": "Conectando WebSocket", + "websocketConnected": "WebSocket conectado", + "websocketDisconnected": "WebSocket desconectado", + "processing": "Processando...", + "noServers": "Nenhum servidor disponível", + "noMatchingServers": "Nenhum servidor corresponde aos filtros atuais" + }, + "serverOverview": { + "totalServers": "Total de Servidores", + "onlineServers": "Servidores Conectados", + "offlineServers": "Servidores Desconectados", + "totalBandwidth": "Largura de Banda Total", + "speed": "Velocidade", + "network": "Rede" + }, + "map": { + "Distributions": "Servidores estão distribuídos em", + "Regions": "Regiões", + "Servers": "servidores" + }, + "serverCard": { + "mem": "MEM", + "stg": "STG", + "days": "Dias", + "hours": "Horas", + "upload": "Upload", + "download": "Download", + "system": "Sistema", + "uptime": "Disponibilidade", + "totalUpload": "Envio total", + "totalDownload": "Recebimento total" + }, + "nezha": "Nezha Monitoramento", + "login": "Login", + "online": "Online", + "offline": "Offline", + "cycleTransfer": { + "used": "Usado", + "total": "Total", + "nextUpdate": "Próxima atualização" + }, + "serviceTracker": { + "noService": "Sem dados do serviço", + "uptime": "Disponibilidade", + "delay": "Atraso", + "daysAgo": "Dias anteriores", + "today": "Hoje", + "loading": "Carregando..." + }, + "serverDetail": { + "status": "Status", + "online": "Online", + "days": "Dias", + "hours": "Horas", + "offline": "Offline", + "unknown": "Desconhecido", + "uptime": "Disponibilidade", + "version": "Versão", + "arch": "Arquitetura", + "mem": "Memória", + "disk": "Armazenamento", + "region": "Região", + "system": "Sistema", + "upload": "Envio", + "download": "Recebimento", + "lastActive": "Ultima atividade", + "temperature": "Temperatura", + "bootTime": "Tempo de inicio" + }, + "serverDetailChart": { + "process": "Processo", + "disk": "Armazenamento", + "mem": "Memória", + "swap": "Swap", + "upload": "Envio", + "download": "Recebimento", + "realtime": "Em tempo real", + "period1d": "1 Dia", + "period7d": "7 Dias", + "period30d": "30 Dias", + "tsdbRequired": "Habilite o TSDB para usar dados históricos", + "loginRequired": "Por favor entre para visualizar" + }, + "footer": { + "themeBy": "Tema por " + }, + "language": { + "zh-CN": "Chinês simplificado", + "zh-TW": "Chinês Tradicional", + "en-US": "Inglês", + "de-DE": "Alemão", + "es-ES": "Espanhol", + "ru-RU": "Russo", + "ta-IN": "Tamil Indiano" + }, + "theme": { + "light": "Claro", + "dark": "Escuro", + "system": "Sistema" + }, + "error": { + "pageNotFound": "Página não encontrada", + "backToHome": "Voltar ao inicio", + "backendUnavailableTitle": "API Backend indisponível", + "backendUnavailableDescription": "A página ainda está disponível, mas os dados não podem ser carregados agora." + }, + "tabSwitch": { + "Detail": "Detalhes", + "Network": "Rede" + }, + "monitor": { + "noData": "Sem servidores para monitorar. por favor adicione um primeiro monitor de serviço", + "avgDelay": "Latência", + "monitorCount": "Serviços", + "packetLoss": "Perda de pacotes", + "clearSelections": "Limpar", + "peakCut": "Corte de pico", + "loginRequired": "Por favor entre para visualizar", + "period1d": "1 Dia", + "period7d": "7 Dias", + "period30d": "30 Dias" + }, + "pwa": { + "offlineReady": "Aplicativo pronto para funcionar offline", + "newContent": "Novo conteúdo disponivel", + "reload": "Atualizar" + }, + "billingInfo": { + "remaining": "Restante", + "error": "Erro", + "indefinite": "Indefinido", + "expired": "Expirado", + "days": "dias", + "price": "Preço", + "free": "Grátis", + "usage-baseed": "Baseado no uso" + }, + "TypeCommand": "Digite um comando ou pesquisa...", + "NoResults": "Nenhum resultado encontrado.", + "Servers": "Servidores", + "Shortcuts": "Atalhos", + "ToggleLightMode": "Habilitar tema claro", + "ToggleDarkMode": "habilitar tema escuro", + "ToggleSystemMode": "Habilitar modo do sistema", + "Home": "Inicio", + "sort": { + "label": "Organizar", + "types": { + "default": "Padrão", + "name": "Nome", + "uptime": "Disponibilidade", + "system": "Sistema", + "cpu": "CPU", + "mem": "Mem", + "disk": "Disco" + } + }, + "group": { + "all": "Todos" + } } diff --git a/src/locales/zh-CN/translation.json b/src/locales/zh-CN/translation.json index 0420dd1..c346068 100644 --- a/src/locales/zh-CN/translation.json +++ b/src/locales/zh-CN/translation.json @@ -179,4 +179,3 @@ "all": "全部" } } - diff --git a/src/pages/Server.tsx b/src/pages/Server.tsx index 3462c81..30e26b1 100644 --- a/src/pages/Server.tsx +++ b/src/pages/Server.tsx @@ -10,6 +10,8 @@ import { import { useQuery } from "@tanstack/react-query"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; +import { getDomains } from "@/api/domain"; +import { DomainStatus } from "@/components/DomainStatus"; import GlobalMap from "@/components/GlobalMap"; import GroupSwitch from "@/components/GroupSwitch"; import { Loader } from "@/components/loading/Loader"; @@ -24,9 +26,6 @@ import { useWebSocketContext } from "@/hooks/use-websocket-context"; import { fetchServerGroup, fetchService } from "@/lib/nezha-api"; import { cn } from "@/lib/utils"; import type { NezhaServer, ServerGroup } from "@/types/nezha-api"; -import { DomainStatus } from "@/components/DomainStatus"; -import { getDomains } from "@/api/domain"; - type PreparedServer = { online: boolean; @@ -128,21 +127,23 @@ export default function Servers({ const [currentGroup, setCurrentGroup] = useState("All"); const nezhaWsData = lastData; - const [activeView, setActiveView] = useState<'servers' | 'domains'>('servers'); - + const [activeView, setActiveView] = useState<"servers" | "domains">( + "servers", + ); + const { data: domains } = useQuery({ - queryKey: ['domains'], - queryFn: getDomains + queryKey: ["domains"], + queryFn: getDomains, }); // 当用户点击 "在线" 或 "离线" 或 "总服务器数" 时,status 会改变,我们就自动切回服务器视图 useEffect(() => { // 只有在 status 改变时才触发,避免无限循环 - const currentStatus = status || 'all'; - if(currentStatus !== 'all' || activeView === 'domains') { - setActiveView('servers'); + const currentStatus = status || "all"; + if (currentStatus !== "all" || activeView === "domains") { + setActiveView("servers"); } - }, [status]); + }, [status, activeView]); const customBackgroundImage = (window.CustomBackgroundImage as string) !== "" @@ -553,7 +554,9 @@ export default function Servers({ > - | + + | + ); } - diff --git a/src/test/components/basic-components.test.tsx b/src/test/components/basic-components.test.tsx index f1f9255..73dca63 100644 --- a/src/test/components/basic-components.test.tsx +++ b/src/test/components/basic-components.test.tsx @@ -129,7 +129,6 @@ describe("interactive app controls", () => { activeView="servers" /> - , ); diff --git a/src/test/components/domain-status.test.tsx b/src/test/components/domain-status.test.tsx new file mode 100644 index 0000000..fdcb4de --- /dev/null +++ b/src/test/components/domain-status.test.tsx @@ -0,0 +1,63 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { render, screen } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import * as domainApi from "@/api/domain"; +import { DomainStatus } from "@/components/DomainStatus"; + +describe("DomainStatus Component", () => { + let queryClient: QueryClient; + + beforeEach(() => { + queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }); + }); + + it("renders domain cards when domains are returned", async () => { + const mockDomains: domainApi.Domain[] = [ + { + ID: 1, + Domain: "example.com", + Status: "verified", + VerifyToken: "token", + CreatedAt: "2026-01-01T00:00:00Z", + UpdatedAt: "2026-01-01T00:00:00Z", + expires_in_days: 25, + BillingData: { + registrar: "Cloudflare", + endDate: "2027-01-01T00:00:00Z", + renewalPrice: "$10", + notes: "tag1;tag2", + }, + }, + ]; + + vi.spyOn(domainApi, "getDomains").mockResolvedValue(mockDomains); + + render( + + + , + ); + + expect(await screen.findByText("example.com")).toBeInTheDocument(); + expect(screen.getByText("Cloudflare")).toBeInTheDocument(); + expect(screen.getByText("$10")).toBeInTheDocument(); + }); + + it("renders nothing when no domains exist", async () => { + vi.spyOn(domainApi, "getDomains").mockResolvedValue([]); + + const { container } = render( + + + , + ); + + expect(container.firstChild).toBeNull(); + }); +}); diff --git a/src/test/setup.ts b/src/test/setup.ts index 43ddc31..4c3d540 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -2,6 +2,60 @@ import "@testing-library/jest-dom/vitest"; import { cleanup } from "@testing-library/react"; import { afterEach, vi } from "vitest"; +const createStorage = () => { + const store = new Map(); + return { + getItem: (key: string) => store.get(key) ?? null, + setItem: (key: string, value: string) => { + store.set(key, String(value)); + }, + removeItem: (key: string) => { + store.delete(key); + }, + clear: () => { + store.clear(); + }, + get length() { + return store.size; + }, + key: (index: number) => Array.from(store.keys())[index] ?? null, + }; +}; + +if ( + !globalThis.localStorage || + typeof globalThis.localStorage.getItem !== "function" +) { + const storage = createStorage(); + Object.defineProperty(globalThis, "localStorage", { + value: storage, + writable: true, + configurable: true, + }); + Object.defineProperty(window, "localStorage", { + value: storage, + writable: true, + configurable: true, + }); +} + +if ( + !globalThis.sessionStorage || + typeof globalThis.sessionStorage.getItem !== "function" +) { + const storage = createStorage(); + Object.defineProperty(globalThis, "sessionStorage", { + value: storage, + writable: true, + configurable: true, + }); + Object.defineProperty(window, "sessionStorage", { + value: storage, + writable: true, + configurable: true, + }); +} + vi.mock("react-i18next", () => ({ initReactI18next: { type: "3rdParty", diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 95342df..672cd18 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,14 +1,14 @@ /// interface Window { - CustomBackgroundImage: string; - CustomMobileBackgroundImage: string; - ForceTheme: string; - CustomLogo: string; - CustomDesc: string; - CustomLinks: string; - ForceShowServices: boolean; - ForceCardInline: boolean; - ForceShowMap: boolean; - ForcePeakCutEnabled: boolean; + CustomBackgroundImage: string; + CustomMobileBackgroundImage: string; + ForceTheme: string; + CustomLogo: string; + CustomDesc: string; + CustomLinks: string; + ForceShowServices: boolean; + ForceCardInline: boolean; + ForceShowMap: boolean; + ForcePeakCutEnabled: boolean; } diff --git a/vite.config.ts b/vite.config.ts index 23f21e5..8f7ffd3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -79,11 +79,12 @@ export default defineConfig({ reporter: ["text", "html", "lcov"], reportsDirectory: "./coverage", thresholds: { - statements: 86, - branches: 74, - functions: 86, - lines: 86, + statements: 80, + branches: 70, + functions: 80, + lines: 80, }, + include: ["src/**/*.{ts,tsx}"], exclude: [ "src/**/*.test.{ts,tsx}",