perf: refactor server data fetch

This commit is contained in:
hamster1963 2024-12-26 13:20:49 +08:00
parent 0c3479bb3b
commit 6b52a8dedb
7 changed files with 86 additions and 49 deletions

View File

@ -1,15 +1,15 @@
"use client" "use client"
import { NezhaAPISafe, ServerApi } from "@/app/types/nezha-api" import { useServerData } from "@/app/lib/server-data-context"
import { NezhaAPISafe } from "@/app/types/nezha-api"
import { ServerDetailChartLoading } from "@/components/loading/ServerDetailLoading" import { ServerDetailChartLoading } from "@/components/loading/ServerDetailLoading"
import AnimatedCircularProgressBar from "@/components/ui/animated-circular-progress-bar" import AnimatedCircularProgressBar from "@/components/ui/animated-circular-progress-bar"
import { Card, CardContent } from "@/components/ui/card" import { Card, CardContent } from "@/components/ui/card"
import { ChartConfig, ChartContainer } from "@/components/ui/chart" import { ChartConfig, ChartContainer } from "@/components/ui/chart"
import { formatBytes, formatNezhaInfo, formatRelativeTime, nezhaFetcher } from "@/lib/utils" import { formatBytes, formatNezhaInfo, formatRelativeTime } from "@/lib/utils"
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { Area, AreaChart, CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts" import { Area, AreaChart, CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts"
import useSWRImmutable from "swr/immutable"
type cpuChartData = { type cpuChartData = {
timeStamp: string timeStamp: string
@ -52,16 +52,9 @@ export default function ServerDetailChartClient({
}) { }) {
const t = useTranslations("ServerDetailChartClient") const t = useTranslations("ServerDetailChartClient")
const { data: allFallbackData } = useSWRImmutable<ServerApi>("/api/server", nezhaFetcher) const { data: serverList, error } = useServerData()
const fallbackData = allFallbackData?.result?.find((item) => item.id === server_id)
const { data, error } = useSWRImmutable<NezhaAPISafe>( const data = serverList?.result?.find((item) => item.id === server_id)
`/api/detail?server_id=${server_id}`,
nezhaFetcher,
{
fallbackData,
},
)
if (error) { if (error) {
return ( return (

View File

@ -1,18 +1,15 @@
"use client" "use client"
import { NezhaAPISafe, ServerApi } from "@/app/types/nezha-api" import { useServerData } from "@/app/lib/server-data-context"
import { BackIcon } from "@/components/Icon" import { BackIcon } from "@/components/Icon"
import ServerFlag from "@/components/ServerFlag" import ServerFlag from "@/components/ServerFlag"
import { ServerDetailLoading } from "@/components/loading/ServerDetailLoading" import { ServerDetailLoading } from "@/components/loading/ServerDetailLoading"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Card, CardContent } from "@/components/ui/card" import { Card, CardContent } from "@/components/ui/card"
import getEnv from "@/lib/env-entry" import { cn, formatBytes } from "@/lib/utils"
import { cn, formatBytes, nezhaFetcher } from "@/lib/utils"
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
import { notFound, useRouter } from "next/navigation" import { notFound, useRouter } from "next/navigation"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import useSWR from "swr"
import useSWRImmutable from "swr/immutable"
export default function ServerDetailClient({ server_id }: { server_id: number }) { export default function ServerDetailClient({ server_id }: { server_id: number }) {
const t = useTranslations("ServerDetailClient") const t = useTranslations("ServerDetailClient")
@ -39,24 +36,13 @@ export default function ServerDetailClient({ server_id }: { server_id: number })
} }
} }
const { data: allFallbackData, isLoading } = useSWRImmutable<ServerApi>( const { data: serverList, error, isLoading } = useServerData()
"/api/server", const data = serverList?.result?.find((item) => item.id === server_id)
nezhaFetcher,
)
const fallbackData = allFallbackData?.result?.find((item) => item.id === server_id)
if (!fallbackData && !isLoading) { if (!data && !isLoading) {
notFound() notFound()
} }
const { data, error } = useSWR<NezhaAPISafe>(`/api/detail?server_id=${server_id}`, nezhaFetcher, {
refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 5000,
dedupingInterval: 1000,
fallbackData,
revalidateOnMount: false,
revalidateIfStale: false,
})
if (error) { if (error) {
return ( return (
<> <>

View File

@ -1,8 +1,6 @@
"use client" "use client"
import { ServerApi } from "@/app/types/nezha-api" import { useServerData } from "@/app/lib/server-data-context"
import { nezhaFetcher } from "@/lib/utils"
import useSWRImmutable from "swr/immutable"
import GlobalLoading from "../../../../components/loading/GlobalLoading" import GlobalLoading from "../../../../components/loading/GlobalLoading"
import { geoJsonString } from "../../../../lib/geo-json-string" import { geoJsonString } from "../../../../lib/geo-json-string"
@ -11,7 +9,7 @@ import GlobalInfo from "./GlobalInfo"
import { InteractiveMap } from "./InteractiveMap" import { InteractiveMap } from "./InteractiveMap"
export default function ServerGlobal() { export default function ServerGlobal() {
const { data: nezhaServerList, error } = useSWRImmutable<ServerApi>("/api/server", nezhaFetcher) const { data: nezhaServerList, error } = useServerData()
if (error) if (error)
return ( return (

View File

@ -1,18 +1,17 @@
"use client" "use client"
import { ServerApi } from "@/app/types/nezha-api" import { useServerData } from "@/app/lib/server-data-context"
import ServerCard from "@/components/ServerCard" import ServerCard from "@/components/ServerCard"
import ServerCardInline from "@/components/ServerCardInline" import ServerCardInline from "@/components/ServerCardInline"
import Switch from "@/components/Switch" import Switch from "@/components/Switch"
import getEnv from "@/lib/env-entry" import getEnv from "@/lib/env-entry"
import { useFilter } from "@/lib/network-filter-context" import { useFilter } from "@/lib/network-filter-context"
import { useStatus } from "@/lib/status-context" import { useStatus } from "@/lib/status-context"
import { cn, nezhaFetcher } from "@/lib/utils" import { cn } from "@/lib/utils"
import { MapIcon, ViewColumnsIcon } from "@heroicons/react/20/solid" import { MapIcon, ViewColumnsIcon } from "@heroicons/react/20/solid"
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
import dynamic from "next/dynamic" import dynamic from "next/dynamic"
import { useEffect, useRef, useState } from "react" import { useEffect, useRef, useState } from "react"
import useSWR from "swr"
import GlobalLoading from "../../../../components/loading/GlobalLoading" import GlobalLoading from "../../../../components/loading/GlobalLoading"
@ -70,10 +69,7 @@ export default function ServerListClient() {
} }
}, []) }, [])
const { data, error } = useSWR<ServerApi>("/api/server", nezhaFetcher, { const { data, error } = useServerData()
refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000,
dedupingInterval: 1000,
})
if (error) if (error)
return ( return (

View File

@ -1,31 +1,30 @@
"use client" "use client"
import { ServerApi } from "@/app/types/nezha-api" import { useServerData } from "@/app/lib/server-data-context"
import { Loader } from "@/components/loading/Loader" import { Loader } from "@/components/loading/Loader"
import { Card, CardContent } from "@/components/ui/card" import { Card, CardContent } from "@/components/ui/card"
import getEnv from "@/lib/env-entry" import getEnv from "@/lib/env-entry"
import { useFilter } from "@/lib/network-filter-context" import { useFilter } from "@/lib/network-filter-context"
import { useStatus } from "@/lib/status-context" import { useStatus } from "@/lib/status-context"
import { cn, formatBytes, nezhaFetcher } from "@/lib/utils" import { cn, formatBytes } from "@/lib/utils"
import blogMan from "@/public/blog-man.webp" import blogMan from "@/public/blog-man.webp"
import { ArrowDownCircleIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid" import { ArrowDownCircleIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid"
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
import Image from "next/image" import Image from "next/image"
import useSWRImmutable from "swr/immutable"
export default function ServerOverviewClient() { export default function ServerOverviewClient() {
const { data, error, isLoading } = useServerData()
const { status, setStatus } = useStatus() const { status, setStatus } = useStatus()
const { filter, setFilter } = useFilter() const { filter, setFilter } = useFilter()
const t = useTranslations("ServerOverviewClient") const t = useTranslations("ServerOverviewClient")
const { data, error, isLoading } = useSWRImmutable<ServerApi>("/api/server", nezhaFetcher)
const disableCartoon = getEnv("NEXT_PUBLIC_DisableCartoon") === "true" const disableCartoon = getEnv("NEXT_PUBLIC_DisableCartoon") === "true"
if (error) { if (error) {
const errorInfo = error as any
return ( return (
<div className="flex flex-col items-center justify-center"> <div className="flex flex-col items-center justify-center">
<p className="text-sm font-medium opacity-40"> <p className="text-sm font-medium opacity-40">
Error status:{error.status} {error.info?.cause ?? error.message} Error status:{errorInfo?.status} {errorInfo.info?.cause ?? errorInfo?.message}
</p> </p>
<p className="text-sm font-medium opacity-40">{t("error_message")}</p> <p className="text-sm font-medium opacity-40">{t("error_message")}</p>
</div> </div>

View File

@ -1,5 +1,6 @@
import Footer from "@/app/(main)/footer" import Footer from "@/app/(main)/footer"
import Header from "@/app/(main)/header" import Header from "@/app/(main)/header"
import { ServerDataProvider } from "@/app/lib/server-data-context"
import { auth } from "@/auth" import { auth } from "@/auth"
import { SignIn } from "@/components/SignIn" import { SignIn } from "@/components/SignIn"
import getEnv from "@/lib/env-entry" import getEnv from "@/lib/env-entry"
@ -13,7 +14,9 @@ export default function MainLayout({ children }: DashboardProps) {
<div className="flex min-h-screen w-full flex-col"> <div className="flex min-h-screen w-full flex-col">
<main className="flex min-h-[calc(100vh-calc(var(--spacing)*16))] flex-1 flex-col gap-4 bg-background p-4 md:p-10 md:pt-8"> <main className="flex min-h-[calc(100vh-calc(var(--spacing)*16))] flex-1 flex-col gap-4 bg-background p-4 md:p-10 md:pt-8">
<Header /> <Header />
<AuthProtected>{children}</AuthProtected> <AuthProtected>
<ServerDataProvider>{children}</ServerDataProvider>
</AuthProtected>
<Footer /> <Footer />
</main> </main>
</div> </div>

View File

@ -0,0 +1,62 @@
"use client"
import { ServerApi } from "@/app/types/nezha-api"
import getEnv from "@/lib/env-entry"
import { nezhaFetcher } from "@/lib/utils"
import { ReactNode, createContext, useContext, useEffect, useState } from "react"
import useSWR from "swr"
interface ServerDataWithTimestamp {
timestamp: number
data: ServerApi
}
interface ServerDataContextType {
data: ServerApi | undefined
error: Error | undefined
isLoading: boolean
history: ServerDataWithTimestamp[]
}
const ServerDataContext = createContext<ServerDataContextType | undefined>(undefined)
const MAX_HISTORY_LENGTH = 30
export function ServerDataProvider({ children }: { children: ReactNode }) {
const [history, setHistory] = useState<ServerDataWithTimestamp[]>([])
const { data, error, isLoading } = useSWR<ServerApi>("/api/server", nezhaFetcher, {
refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000,
dedupingInterval: 1000,
})
useEffect(() => {
if (data) {
setHistory((prev) => {
const newHistory = [
{
timestamp: Date.now(),
data: data,
},
...prev,
].slice(0, MAX_HISTORY_LENGTH)
return newHistory
})
}
}, [data])
return (
<ServerDataContext.Provider value={{ data, error, isLoading, history }}>
{children}
</ServerDataContext.Provider>
)
}
export function useServerData() {
const context = useContext(ServerDataContext)
if (context === undefined) {
throw new Error("useServerData must be used within a ServerDataProvider")
}
return context
}