mirror of
https://github.com/hamster1963/nezha-dash.git
synced 2025-04-24 21:10:45 +08:00
Merge branch 'main' into cloudflare
This commit is contained in:
commit
2c3057cbef
@ -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
|
||||||
|
|
||||||
### 部署
|
### 部署
|
||||||
|
|
||||||
|
@ -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 } = {};
|
||||||
|
@ -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>
|
||||||
|
@ -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} />
|
||||||
|
@ -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}
|
||||||
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
12
package.json
12
package.json
@ -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"
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user