From 9ffe4c8995c4858d175cbd14f1cdf72d64259610 Mon Sep 17 00:00:00 2001 From: hamster1963 <1410514192@qq.com> Date: Sun, 28 Jul 2024 16:03:49 +0800 Subject: [PATCH 1/3] perf: use swr to prefetch data --- README.md | 4 +- app/(main)/page.tsx | 21 ++++++---- app/api/server/route.ts | 90 ++++++++++++++++++++-------------------- app/types/utils.ts | 3 +- bun.lockb | Bin 404297 -> 406649 bytes lib/utils.ts | 47 ++++++++++++--------- 6 files changed, 91 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index ed1f032..aa26433 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,11 @@ ### 一键部署到 Vercel + [部署简易教程](https://buycoffee.top/blog/tech/nezha)

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fhamster1963%2Fnezha-dash&env=NezhaBaseUrl,NezhaAuth&project-name=nezha-dash&repository-name=nezha-dash) - - - ![screen-shot-one](/.github/shotOne.png) ![screen-shot-two](/.github/shotTwo.png) diff --git a/app/(main)/page.tsx b/app/(main)/page.tsx index 26e7e08..e7dcb53 100644 --- a/app/(main)/page.tsx +++ b/app/(main)/page.tsx @@ -1,14 +1,21 @@ import ServerList from "@/components/ServerList"; import ServerOverview from "@/components/ServerOverview"; - - +import { GetNezhaData } from "@/lib/prefetch"; +import { SWRConfig } from "swr"; export default function Home() { - return ( -
- - -
+ +
+ + +
+
); } diff --git a/app/api/server/route.ts b/app/api/server/route.ts index b08f38a..08789e0 100644 --- a/app/api/server/route.ts +++ b/app/api/server/route.ts @@ -1,56 +1,58 @@ - import { NezhaAPI, ServerApi } from "@/app/types/nezha-api"; import { MakeOptional } from "@/app/types/utils"; import { NextResponse } from "next/server"; -import { DateTime } from "luxon"; export async function GET(_: Request) { - if (!process.env.NezhaBaseUrl) { - return NextResponse.json({ error: 'NezhaBaseUrl is not set' }, { status: 400 }) - } + if (!process.env.NezhaBaseUrl) { + return NextResponse.json( + { error: "NezhaBaseUrl is not set" }, + { status: 400 }, + ); + } - // Remove trailing slash - var nezhaBaseUrl = process.env.NezhaBaseUrl; + // Remove trailing slash + var nezhaBaseUrl = process.env.NezhaBaseUrl; - if (process.env.NezhaBaseUrl[process.env.NezhaBaseUrl.length - 1] === '/') { - nezhaBaseUrl = process.env.NezhaBaseUrl.slice(0, -1); - } + if (process.env.NezhaBaseUrl[process.env.NezhaBaseUrl.length - 1] === "/") { + nezhaBaseUrl = process.env.NezhaBaseUrl.slice(0, -1); + } - try { - const response = await fetch(nezhaBaseUrl+ '/api/v1/server/details',{ - headers: { - 'Authorization': process.env.NezhaAuth as string - }, - next:{ - revalidate:1 - } - }); - const nezhaData = (await response.json()).result as NezhaAPI[]; - const data: ServerApi = { - live_servers: 0, - offline_servers: 0, - total_bandwidth: 0, - result: [] + try { + const response = await fetch(nezhaBaseUrl + "/api/v1/server/details", { + headers: { + Authorization: process.env.NezhaAuth as string, + }, + next: { + revalidate: 0, + }, + }); + const nezhaData = (await response.json()).result as NezhaAPI[]; + const data: ServerApi = { + live_servers: 0, + offline_servers: 0, + total_bandwidth: 0, + result: [], + }; + const timestamp = Date.now() / 1000; + data.result = nezhaData.map( + (element: MakeOptional) => { + if (timestamp - element.last_active > 300) { + data.offline_servers += 1; + } else { + data.live_servers += 1; } + data.total_bandwidth += element.status.NetOutTransfer; - data.result = nezhaData.map((element: MakeOptional) => { - if (DateTime.now().toUnixInteger() - element.last_active > 300) { - data.offline_servers += 1; - } else { - data.live_servers += 1; - } - data.total_bandwidth += element.status.NetOutTransfer; + delete element.ipv4; + delete element.ipv6; + delete element.valid_ip; - delete element.ipv4; - delete element.ipv6; - delete element.valid_ip; + return element; + }, + ); - return element; - }); - - return NextResponse.json(data, { status: 200 }) - } catch (error) { - return NextResponse.json({ error: error }, { status: 200 }) - } - -} \ No newline at end of file + return NextResponse.json(data, { status: 200 }); + } catch (error) { + return NextResponse.json({ error: error }, { status: 200 }); + } +} diff --git a/app/types/utils.ts b/app/types/utils.ts index 488df36..69b6f87 100644 --- a/app/types/utils.ts +++ b/app/types/utils.ts @@ -1 +1,2 @@ -export type MakeOptional = Omit & Partial>; +export type MakeOptional = Omit & + Partial>; diff --git a/bun.lockb b/bun.lockb index 23316e6136883e793310df82cc773ab848c302a1..113abeb9e012407befd121e809d01d532615897f 100755 GIT binary patch delta 15016 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 hXes6J_li&xgS@ }); export const nezhaFetcher = (url: string) => - fetch(url) - .then((res) => { - if (!res.ok) { - throw new Error(res.statusText); - } - return res.json(); - }) - .then((data) => data) - .catch((err) => { - console.error(err); - throw err; - }); - \ No newline at end of file + fetch(url) + .then((res) => { + if (!res.ok) { + throw new Error(res.statusText); + } + return res.json(); + }) + .then((data) => data) + .catch((err) => { + console.error(err); + throw err; + }); From 94aa61f5b7bac301e45cf9bfa91ee3c73d1f4b06 Mon Sep 17 00:00:00 2001 From: hamster1963 <1410514192@qq.com> Date: Sun, 28 Jul 2024 16:05:00 +0800 Subject: [PATCH 2/3] fix: update now time --- .../ClientComponents/ServerListClient.tsx | 36 ++-- .../ClientComponents/ServerOverviewClient.tsx | 156 ++++++++++-------- app/(main)/footer.tsx | 40 ++--- app/(main)/header.tsx | 33 +++- app/(main)/nav.tsx | 57 ------- components/ServerCard.tsx | 10 +- components/ServerList.tsx | 6 +- components/ServerOverview.tsx | 11 +- components/SubscribeCard.tsx | 67 -------- lib/prefetch.tsx | 54 ++++++ package.json | 8 +- 11 files changed, 223 insertions(+), 255 deletions(-) delete mode 100644 app/(main)/nav.tsx delete mode 100644 components/SubscribeCard.tsx create mode 100644 lib/prefetch.tsx diff --git a/app/(main)/ClientComponents/ServerListClient.tsx b/app/(main)/ClientComponents/ServerListClient.tsx index 2f0765b..d899f4e 100644 --- a/app/(main)/ClientComponents/ServerListClient.tsx +++ b/app/(main)/ClientComponents/ServerListClient.tsx @@ -4,33 +4,31 @@ import { ServerApi } from "@/app/types/nezha-api"; import ServerCard from "@/components/ServerCard"; import { nezhaFetcher } from "@/lib/utils"; import useSWR from "swr"; -import { DateTime } from "luxon"; export default function ServerListClient() { - const { data } = useSWR('/api/server', nezhaFetcher, { + const { data } = useSWR("/api/server", nezhaFetcher, { refreshInterval: 3000, }); if (!data) return null; const sortedResult = data.result.sort((a: any, b: any) => a.id - b.id); + const timestamp = Date.now() / 1000; return ( -
- {sortedResult.map( - (server: any) => ( - 300 ? "offline" : "online"} - uptime={server.status.Uptime / 86400} - mem={(server.status.MemUsed / server.host.MemTotal) * 100} - stg={(server.status.DiskUsed / server.host.DiskTotal) * 100} - /> - ), - )} +
+ {sortedResult.map((server: any) => ( + 300 ? "offline" : "online"} + uptime={server.status.Uptime / 86400} + mem={(server.status.MemUsed / server.host.MemTotal) * 100} + stg={(server.status.DiskUsed / server.host.DiskTotal) * 100} + /> + ))}
); } diff --git a/app/(main)/ClientComponents/ServerOverviewClient.tsx b/app/(main)/ClientComponents/ServerOverviewClient.tsx index 8ec1b76..d64c1d5 100644 --- a/app/(main)/ClientComponents/ServerOverviewClient.tsx +++ b/app/(main)/ClientComponents/ServerOverviewClient.tsx @@ -9,70 +9,94 @@ import { Loader } from "@/components/loading/Loader"; import { ServerApi } from "@/app/types/nezha-api"; export default function ServerOverviewClient() { - const { data } = useSWR('/api/server', nezhaFetcher, { - refreshInterval: 30000 - }); + const { data } = useSWR("/api/server", nezhaFetcher, { + refreshInterval: 30000, + }); - return ( -
- - -
-

Total servers

-
- - - - {data ?

{data?.result.length}

:
} -
-
-
-
- - -
-

Online servers

-
- - - - - {data ?

{data?.live_servers}

:
} -
-
-
-
- - -
-

Offline servers

-
- - - - - {data ?

{data?.offline_servers}

:
} -
- - -
-
-
- - -
-

Total bandwidth

- {data ?

{formatBytes(data?.total_bandwidth)}

:
} -
- {'Hamster1963'} -
-
-
- ) -} \ No newline at end of file + return ( +
+ + +
+

Total servers

+
+ + + + {data ? ( +

{data?.result.length}

+ ) : ( +
+ +
+ )} +
+
+
+
+ + +
+

Online servers

+
+ + + + + {data ? ( +

{data?.live_servers}

+ ) : ( +
+ +
+ )} +
+
+
+
+ + +
+

Offline servers

+
+ + + + + {data ? ( +

{data?.offline_servers}

+ ) : ( +
+ +
+ )} +
+
+
+
+ + +
+

Total bandwidth

+ {data ? ( +

+ {formatBytes(data?.total_bandwidth)} +

+ ) : ( +
+ +
+ )} +
+ {"Hamster1963"} +
+
+
+ ); +} diff --git a/app/(main)/footer.tsx b/app/(main)/footer.tsx index a14f070..2792eb3 100644 --- a/app/(main)/footer.tsx +++ b/app/(main)/footer.tsx @@ -1,22 +1,22 @@ export default function Footer() { - return ( - - ) + return ( + + ); } diff --git a/app/(main)/header.tsx b/app/(main)/header.tsx index f062240..65702ba 100644 --- a/app/(main)/header.tsx +++ b/app/(main)/header.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import Image from "next/image"; import { Separator } from "@/components/ui/separator"; import { DateTime } from "luxon"; @@ -36,22 +36,45 @@ function Header() { ); } +// https://github.com/streamich/react-use/blob/master/src/useInterval.ts +const useInterval = (callback: Function, delay?: number | null) => { + const savedCallback = useRef(() => { }); + + useEffect(() => { + savedCallback.current = callback; + }); + + useEffect(() => { + if (delay !== null) { + const interval = setInterval(() => savedCallback.current(), delay || 0); + return () => clearInterval(interval); + } + + return undefined; + }, [delay]); +}; + function Overview() { const [mouted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); - const time = DateTime.TIME_SIMPLE; - time.hour12 = true; + const timeOption = DateTime.TIME_SIMPLE; + timeOption.hour12 = true; + const [timeString, setTimeString] = useState(DateTime.now().setLocale("en-US").toLocaleString(timeOption)); + + useInterval(() => { + setTimeString(DateTime.now().setLocale("en-US").toLocaleString(timeOption)); + }, 1000); return ( -
+

👋 Overview

where the time is

{mouted && (

- {DateTime.now().setLocale("en-US").toLocaleString(time)} + {timeString}

)}
diff --git a/app/(main)/nav.tsx b/app/(main)/nav.tsx deleted file mode 100644 index 3f8b907..0000000 --- a/app/(main)/nav.tsx +++ /dev/null @@ -1,57 +0,0 @@ -"use client"; - -import { clsx } from "clsx"; -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import React from "react"; - -import { cn } from "@/lib/utils"; - -export const siteUrlList = [ - { - name: "Home", - header: "👋 Overview", - url: "/", - }, - { - name: "Service", - header: "🎛️ Service", - url: "/service", - }, -]; -export default function Nav() { - const nowPath = usePathname(); - return ( -
-
- {siteUrlList.map((site, index) => ( -
- {index !== 0 && ( -

- / -

- )} - - {site.name} - -
- ))} -
-
- ); -} diff --git a/components/ServerCard.tsx b/components/ServerCard.tsx index 9397acf..6120137 100644 --- a/components/ServerCard.tsx +++ b/components/ServerCard.tsx @@ -40,9 +40,11 @@ export default function ServerCard({ -
+
-

+

{name}

@@ -90,7 +92,9 @@ export default function ServerCard({ -
+

{name}

diff --git a/components/ServerList.tsx b/components/ServerList.tsx index db72679..14c28d2 100644 --- a/components/ServerList.tsx +++ b/components/ServerList.tsx @@ -2,10 +2,6 @@ import React from "react"; import ServerListClient from "@/app/(main)/ClientComponents/ServerListClient"; - - export default async function ServerList() { - return ( - - ); + return ; } diff --git a/components/ServerOverview.tsx b/components/ServerOverview.tsx index d5523fa..2e61c79 100644 --- a/components/ServerOverview.tsx +++ b/components/ServerOverview.tsx @@ -1,12 +1,5 @@ import ServerOverviewClient from "@/app/(main)/ClientComponents/ServerOverviewClient"; - - - export default async function ServerOverview() { - return ( - - ) - - -} \ No newline at end of file + return ; +} diff --git a/components/SubscribeCard.tsx b/components/SubscribeCard.tsx deleted file mode 100644 index b52d9fa..0000000 --- a/components/SubscribeCard.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from "react"; - -import { Loader } from "@/components/loading/Loader"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Progress } from "@/components/ui/progress"; - -type SubscribeCardProps = { - provider: string; - behavior: string; - value: number; - nextBillDay?: number; - total: number; - unit: string; - colorClassName: string; - isLoading: boolean; -}; - -function SubscribeCard({ - provider, - behavior, - value, - total, - unit, - nextBillDay, - colorClassName, - isLoading, -}: SubscribeCardProps) { - return ( - - - - {provider} - - {nextBillDay !== -1 && ( -
-

- {nextBillDay} -

-

|

-
- )} -
- -
-

{behavior}

- -
- -
-

{value}

-

- {unit} -

-
- -
-
- ); -} - -export default SubscribeCard; diff --git a/lib/prefetch.tsx b/lib/prefetch.tsx new file mode 100644 index 0000000..911edbb --- /dev/null +++ b/lib/prefetch.tsx @@ -0,0 +1,54 @@ +import { NezhaAPI, ServerApi } from "@/app/types/nezha-api"; +import { MakeOptional } from "@/app/types/utils"; +import { error } from "console"; + +export async function GetNezhaData() { + if (!process.env.NezhaBaseUrl) { + error("NezhaBaseUrl is not set"); + return; + } + // Remove trailing slash + var nezhaBaseUrl = process.env.NezhaBaseUrl; + + if (process.env.NezhaBaseUrl[process.env.NezhaBaseUrl.length - 1] === "/") { + nezhaBaseUrl = process.env.NezhaBaseUrl.slice(0, -1); + } + try { + const response = await fetch(nezhaBaseUrl + "/api/v1/server/details", { + headers: { + Authorization: process.env.NezhaAuth as string, + }, + next: { + revalidate: 1, + }, + }); + const nezhaData = (await response.json()).result as NezhaAPI[]; + const data: ServerApi = { + live_servers: 0, + offline_servers: 0, + total_bandwidth: 0, + result: [], + }; + const timestamp = Date.now() / 1000; + data.result = nezhaData.map( + (element: MakeOptional) => { + if (timestamp - element.last_active > 300) { + data.offline_servers += 1; + } else { + data.live_servers += 1; + } + data.total_bandwidth += element.status.NetOutTransfer; + + delete element.ipv4; + delete element.ipv6; + delete element.valid_ip; + + return element; + }, + ); + + return data; + } catch (error) { + return error; + } +} \ No newline at end of file diff --git a/package.json b/package.json index 5deab95..9bcef28 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "private": true, "scripts": { "dev": "next dev -p 3020", - "build": "next build", - "start": "next start", - "lint": "next lint" + "start": "node .next/standalone/server.js", + "lint": "next lint", + "build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/" }, "dependencies": { "@ducanh2912/next-pwa": "^10.2.6", @@ -33,7 +33,7 @@ "react-wrap-balancer": "^1.1.0", "sharp": "^0.33.3", "sonner": "^1.4.41", - "swr": "^2.2.6-beta.3", + "swr": "^2.2.6-beta.4", "tailwind-merge": "^2.2.2", "tailwindcss-animate": "^1.0.7", "zod": "^3.22.4" From 9ee938453f9798ee7b578940796023d02c551238 Mon Sep 17 00:00:00 2001 From: hamster1963 <1410514192@qq.com> Date: Sun, 28 Jul 2024 16:05:08 +0800 Subject: [PATCH 3/3] feat: add Dockerfile --- Dockerfile | 58 +++++++++++++++++++++++++++++++++++++++++++++++++ next.config.mjs | 5 ++++- 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a5cfcf0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,58 @@ +FROM node:21-alpine AS base + +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +RUN apk --no-cache add ca-certificates wget +RUN wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub +RUN wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk +RUN apk add --no-cache --force-overwrite glibc-2.28-r0.apk + +COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* bun.lockb* ./ +RUN \ + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ + elif [ -f package-lock.json ]; then npm ci; \ + elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \ + elif [ -f bun.lockb ]; then npm install -g bun && bun install; \ + else echo "Lockfile not found." && exit 1; \ + fi + + +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +ARG PROD_ENV="" +# Appends to .env.production +RUN printf "$PROD_ENV" >> .env.production + +RUN yarn build + + +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV production +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public +COPY --from=builder /app/.env.production ./.env.production + +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + + + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 +ENV HOSTNAME "0.0.0.0" + +CMD ["node", "server.js"] diff --git a/next.config.mjs b/next.config.mjs index 9b4c182..7e5fcf7 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -18,6 +18,9 @@ const withPWA = withPWAInit({ }); /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + output: "standalone", + reactStrictMode: true, +}; export default bundleAnalyzer(withPWA(nextConfig));