Merge branch 'main' into cloudflare

This commit is contained in:
hamster1963 2024-12-05 15:09:07 +08:00
commit 2c3057cbef
9 changed files with 57 additions and 70 deletions

View File

@ -10,7 +10,7 @@
> V0 | V1 版本 issue 请在当前仓库发起 > V0 | V1 版本 issue 请在当前仓库发起
> [!TIP] > [!TIP]
> 有关 V1 版本 pr 可移步 https://github.com/hamster1963/nezha-dash-react > 有关 V1 版本 pr 可移步 https://github.com/hamster1963/nezha-dash-v1
### 部署 ### 部署

View File

@ -1,11 +1,30 @@
import { GetNezhaData } from "@/lib/serverFetch"; "use client";
import { ServerApi } from "@/app/types/nezha-api";
import { nezhaFetcher } from "@/lib/utils";
import useSWR from "swr";
import { geoJsonString } from "../../../lib/geo-json-string"; import { geoJsonString } from "../../../lib/geo-json-string";
import GlobalInfo from "./GlobalInfo"; import GlobalInfo from "./GlobalInfo";
import GlobalLoading from "./GlobalLoading";
import { InteractiveMap } from "./InteractiveMap"; import { InteractiveMap } from "./InteractiveMap";
export default async function ServerGlobal() { export default function ServerGlobal() {
const nezhaServerList = await GetNezhaData(); const { data: nezhaServerList, error } = useSWR<ServerApi>(
"/api/server",
nezhaFetcher,
);
if (error)
return (
<div className="flex flex-col items-center justify-center">
<p className="text-sm font-medium opacity-40">{error.message}</p>
</div>
);
if (!nezhaServerList) {
return <GlobalLoading />;
}
const countryList: string[] = []; const countryList: string[] = [];
const serverCounts: { [key: string]: number } = {}; const serverCounts: { [key: string]: number } = {};

View File

@ -1,6 +1,5 @@
"use client"; "use client";
import GlobalBackButton from "@/components/GlobalBackButton";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
type GlobalInfoProps = { type GlobalInfoProps = {
@ -11,7 +10,6 @@ export default function GlobalInfo({ countries }: GlobalInfoProps) {
const t = useTranslations("Global"); const t = useTranslations("Global");
return ( return (
<section className="flex items-center justify-between"> <section className="flex items-center justify-between">
<GlobalBackButton />
<p className="text-sm font-medium opacity-40"> <p className="text-sm font-medium opacity-40">
{t("Distributions")} {countries.length} {t("Regions")} {t("Distributions")} {countries.length} {t("Regions")}
</p> </p>

View File

@ -1,6 +1,5 @@
"use client"; "use client";
import GlobalBackButton from "@/components/GlobalBackButton";
import { Loader } from "@/components/loading/Loader"; import { Loader } from "@/components/loading/Loader";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
@ -8,7 +7,6 @@ export default function GlobalLoading() {
const t = useTranslations("Global"); const t = useTranslations("Global");
return ( return (
<section className="flex flex-col gap-4 mt-[3.2px]"> <section className="flex flex-col gap-4 mt-[3.2px]">
<GlobalBackButton />
<div className="flex min-h-40 flex-col items-center justify-center font-medium text-sm"> <div className="flex min-h-40 flex-col items-center justify-center font-medium text-sm">
{t("Loading")} {t("Loading")}
<Loader visible={true} /> <Loader visible={true} />

View File

@ -10,19 +10,25 @@ import { useStatus } from "@/lib/status-context";
import { cn, nezhaFetcher } from "@/lib/utils"; import { cn, nezhaFetcher } 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 { useRouter } from "next/navigation"; import dynamic from "next/dynamic";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import useSWR from "swr"; import useSWR from "swr";
import GlobalLoading from "./GlobalLoading";
const ServerGlobal = dynamic(() => import("./Global"), {
loading: () => <GlobalLoading />,
});
export default function ServerListClient() { export default function ServerListClient() {
const { status } = useStatus(); const { status } = useStatus();
const { filter } = useFilter(); const { filter } = useFilter();
const t = useTranslations("ServerListClient"); const t = useTranslations("ServerListClient");
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const defaultTag = "defaultTag"; const defaultTag = "defaultTag";
const router = useRouter();
const [tag, setTag] = useState<string>(defaultTag); const [tag, setTag] = useState<string>(defaultTag);
const [showMap, setShowMap] = useState<boolean>(false);
const [inline, setInline] = useState<string>("0"); const [inline, setInline] = useState<string>("0");
useEffect(() => { useEffect(() => {
@ -131,9 +137,14 @@ export default function ServerListClient() {
<section className="flex items-center gap-2 w-full overflow-hidden"> <section className="flex items-center gap-2 w-full overflow-hidden">
<button <button
onClick={() => { onClick={() => {
router.push(`/?global=true`); setShowMap(!showMap);
}} }}
className="rounded-[50px] text-white cursor-pointer [text-shadow:_0_1px_0_rgb(0_0_0_/_20%)] bg-blue-600 hover:bg-blue-500 p-[10px] transition-all shadow-[inset_0_1px_0_rgba(255,255,255,0.2)] hover:shadow-[inset_0_1px_0_rgba(0,0,0,0.2)] " className={cn(
"rounded-[50px] text-white cursor-pointer [text-shadow:_0_1px_0_rgb(0_0_0_/_20%)] bg-blue-600 p-[10px] transition-all shadow-[inset_0_1px_0_rgba(255,255,255,0.2)]",
{
"shadow-[inset_0_1px_0_rgba(0,0,0,0.2)] bg-blue-500": showMap,
},
)}
> >
<MapIcon className="size-[13px]" /> <MapIcon className="size-[13px]" />
</button> </button>
@ -161,6 +172,7 @@ export default function ServerListClient() {
/> />
)} )}
</section> </section>
{showMap && <ServerGlobal />}
{inline === "1" && ( {inline === "1" && (
<section <section
ref={containerRef} ref={containerRef}

View File

@ -14,7 +14,6 @@ import {
} from "@heroicons/react/20/solid"; } 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 { useSearchParams } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
export default function ServerOverviewClient() { export default function ServerOverviewClient() {
@ -30,10 +29,6 @@ export default function ServerOverviewClient() {
); );
const disableCartoon = getEnv("NEXT_PUBLIC_DisableCartoon") === "true"; const disableCartoon = getEnv("NEXT_PUBLIC_DisableCartoon") === "true";
const searchParams = useSearchParams();
const global = searchParams.get("global");
if (error) { if (error) {
return ( return (
<div className="flex flex-col items-center justify-center"> <div className="flex flex-col items-center justify-center">
@ -50,14 +45,10 @@ export default function ServerOverviewClient() {
<section className="grid grid-cols-2 gap-4 lg:grid-cols-4"> <section className="grid grid-cols-2 gap-4 lg:grid-cols-4">
<Card <Card
onClick={() => { onClick={() => {
if (!global) {
setFilter(false); setFilter(false);
setStatus("all"); setStatus("all");
}
}} }}
className={cn("cursor-pointer hover:border-blue-500 transition-all", { className={cn("cursor-pointer hover:border-blue-500 transition-all")}
"pointer-events-none": global,
})}
> >
<CardContent className="flex h-full items-center px-6 py-3"> <CardContent className="flex h-full items-center px-6 py-3">
<section className="flex flex-col gap-1"> <section className="flex flex-col gap-1">
@ -83,19 +74,14 @@ export default function ServerOverviewClient() {
</Card> </Card>
<Card <Card
onClick={() => { onClick={() => {
if (!global) {
setFilter(false); setFilter(false);
setStatus("online"); setStatus("online");
}
}} }}
className={cn( className={cn(
"cursor-pointer hover:ring-green-500 ring-1 ring-transparent transition-all", "cursor-pointer hover:ring-green-500 ring-1 ring-transparent transition-all",
{ {
"ring-green-500 ring-2 border-transparent": status === "online", "ring-green-500 ring-2 border-transparent": status === "online",
}, },
{
"pointer-events-none": global,
},
)} )}
> >
<CardContent className="flex h-full items-center px-6 py-3"> <CardContent className="flex h-full items-center px-6 py-3">
@ -123,19 +109,14 @@ export default function ServerOverviewClient() {
</Card> </Card>
<Card <Card
onClick={() => { onClick={() => {
if (!global) {
setFilter(false); setFilter(false);
setStatus("offline"); setStatus("offline");
}
}} }}
className={cn( className={cn(
"cursor-pointer hover:ring-red-500 ring-1 ring-transparent transition-all", "cursor-pointer hover:ring-red-500 ring-1 ring-transparent transition-all",
{ {
"ring-red-500 ring-2 border-transparent": status === "offline", "ring-red-500 ring-2 border-transparent": status === "offline",
}, },
{
"pointer-events-none": global,
},
)} )}
> >
<CardContent className="flex h-full items-center px-6 py-3"> <CardContent className="flex h-full items-center px-6 py-3">
@ -163,19 +144,14 @@ export default function ServerOverviewClient() {
</Card> </Card>
<Card <Card
onClick={() => { onClick={() => {
if (!global) {
setStatus("all"); setStatus("all");
setFilter(true); setFilter(true);
}
}} }}
className={cn( className={cn(
"cursor-pointer hover:ring-purple-500 ring-1 ring-transparent transition-all", "cursor-pointer hover:ring-purple-500 ring-1 ring-transparent transition-all",
{ {
"ring-purple-500 ring-2 border-transparent": filter === true, "ring-purple-500 ring-2 border-transparent": filter === true,
}, },
{
"pointer-events-none": global,
},
)} )}
> >
<CardContent className="flex h-full items-center relative px-6 py-3"> <CardContent className="flex h-full items-center relative px-6 py-3">
@ -202,9 +178,7 @@ export default function ServerOverviewClient() {
</p> </p>
<p className="text-[11px] flex items-center text-nowrap font-semibold"> <p className="text-[11px] flex items-center text-nowrap font-semibold">
<ArrowDownCircleIcon className="size-3 mr-0.5" /> <ArrowDownCircleIcon className="size-3 mr-0.5" />
{formatBytes( {formatBytes(data?.total_in_speed)}/s
data?.total_in_speed,
)}/s
</p> </p>
</section> </section>
</> </>

View File

@ -1,27 +1,13 @@
import ServerList from "@/components/ServerList"; import ServerList from "@/components/ServerList";
import ServerOverview from "@/components/ServerOverview"; import ServerOverview from "@/components/ServerOverview";
import { Suspense } from "react";
import ServerGlobal from "./ClientComponents/Global";
import GlobalLoading from "./ClientComponents/GlobalLoading";
export const runtime = "edge"; export const runtime = "edge";
export default async function Home({ export default async function Home() {
searchParams,
}: {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
const global = (await searchParams).global;
return ( return (
<div className="mx-auto grid w-full max-w-5xl gap-4 md:gap-6"> <div className="mx-auto grid w-full max-w-5xl gap-4 md:gap-6">
<ServerOverview /> <ServerOverview />
{!global && <ServerList />} <ServerList />
{global && (
<Suspense fallback={<GlobalLoading />}>
<ServerGlobal />
</Suspense>
)}
</div> </div>
); );
} }

BIN
bun.lockb

Binary file not shown.

View File

@ -1,6 +1,6 @@
{ {
"name": "nezha-dash", "name": "nezha-dash",
"version": "1.6.11", "version": "1.7.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 3040", "dev": "next dev -p 3040",
@ -26,7 +26,7 @@
"@types/d3-geo": "^3.1.0", "@types/d3-geo": "^3.1.0",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@typescript-eslint/eslint-plugin": "^8.17.0", "@typescript-eslint/eslint-plugin": "^8.17.0",
"caniuse-lite": "^1.0.30001685", "caniuse-lite": "^1.0.30001686",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"country-flag-icons": "^1.5.13", "country-flag-icons": "^1.5.13",
@ -47,7 +47,7 @@
"react-dom": "19.0.0-rc-02c0e824-20241028", "react-dom": "19.0.0-rc-02c0e824-20241028",
"react-intersection-observer": "^9.13.1", "react-intersection-observer": "^9.13.1",
"react-wrap-balancer": "^1.1.1", "react-wrap-balancer": "^1.1.1",
"recharts": "^2.14.0", "recharts": "^2.14.1",
"sharp": "^0.33.5", "sharp": "^0.33.5",
"swr": "^2.2.6-beta.4", "swr": "^2.2.6-beta.4",
"tailwind-merge": "^2.5.5", "tailwind-merge": "^2.5.5",
@ -56,7 +56,7 @@
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "^15.0.3", "@next/bundle-analyzer": "^15.0.3",
"@tailwindcss/postcss": "^4.0.0-beta.4", "@tailwindcss/postcss": "^4.0.0-beta.5",
"@types/node": "^22.10.1", "@types/node": "^22.10.1",
"@types/react": "npm:types-react@19.0.0-rc.1", "@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
@ -65,9 +65,9 @@
"eslint-plugin-turbo": "^2.3.3", "eslint-plugin-turbo": "^2.3.3",
"eslint-plugin-unused-imports": "^4.1.4", "eslint-plugin-unused-imports": "^4.1.4",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"prettier": "^3.4.1", "prettier": "^3.4.2",
"prettier-plugin-tailwindcss": "^0.6.9", "prettier-plugin-tailwindcss": "^0.6.9",
"tailwindcss": "^4.0.0-beta.4", "tailwindcss": "^4.0.0-beta.5",
"typescript": "^5.7.2", "typescript": "^5.7.2",
"vercel": "39.1.1" "vercel": "39.1.1"
}, },