mirror of
https://github.com/hamster1963/nezha-dash.git
synced 2025-04-24 21:10:45 +08:00
perf: refactor server data fetch
This commit is contained in:
parent
0c3479bb3b
commit
6b52a8dedb
@ -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 (
|
||||||
|
@ -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 (
|
||||||
<>
|
<>
|
||||||
|
@ -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 (
|
||||||
|
@ -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 (
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
62
app/lib/server-data-context.tsx
Normal file
62
app/lib/server-data-context.tsx
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user