mirror of
https://github.com/hamster1963/nezha-dash.git
synced 2025-04-24 21:10:45 +08:00
Compare commits
5 Commits
d8312642e4
...
6a0367c192
Author | SHA1 | Date | |
---|---|---|---|
|
6a0367c192 | ||
|
3551bc90cd | ||
|
4133a7f5c7 | ||
|
753c7dec10 | ||
|
722ec67091 |
@ -1,13 +1,12 @@
|
||||
import { countryCodeMapping, reverseCountryCodeMapping } from "@/lib/geo";
|
||||
import { countryCoordinates } from "@/lib/geo-limit";
|
||||
import { GetNezhaData } from "@/lib/serverFetch";
|
||||
import { ServerStackIcon } from "@heroicons/react/20/solid";
|
||||
import * as turf from "@turf/turf";
|
||||
import DottedMap from "dotted-map/without-countries";
|
||||
import Link from "next/link";
|
||||
|
||||
import { geoJsonString } from "../../../lib/geo-json-string";
|
||||
import { mapJsonString } from "../../../lib/map-string";
|
||||
import GlobalInfo from "./GlobalInfo";
|
||||
|
||||
interface GlobalProps {
|
||||
countries?: string[];
|
||||
@ -30,6 +29,7 @@ export default async function ServerGlobal() {
|
||||
}
|
||||
|
||||
export async function Global({ countries = [] }: GlobalProps) {
|
||||
// const t = useTranslations("Global");
|
||||
const map = new DottedMap({ map: JSON.parse(mapJsonString) });
|
||||
|
||||
const countries_alpha3 = countries
|
||||
@ -96,12 +96,7 @@ export async function Global({ countries = [] }: GlobalProps) {
|
||||
|
||||
return (
|
||||
<section className="flex flex-col gap-4 mt-[3.2px]">
|
||||
<Link
|
||||
href={`/`}
|
||||
className="rounded-[50px] w-fit bg-stone-100 p-[10px] transition-all hover:bg-stone-200 dark:hover:bg-stone-700 dark:bg-stone-800"
|
||||
>
|
||||
<ServerStackIcon className="size-4" />
|
||||
</Link>
|
||||
<GlobalInfo countries={countries} />
|
||||
<img
|
||||
src={`data:image/svg+xml;utf8,${encodeURIComponent(finalMap)}`}
|
||||
alt="World Map with Highlighted Countries"
|
||||
|
20
app/(main)/ClientComponents/GlobalInfo.tsx
Normal file
20
app/(main)/ClientComponents/GlobalInfo.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import GlobalBackButton from "@/components/GlobalBackButton";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type GlobalInfoProps = {
|
||||
countries: string[];
|
||||
};
|
||||
|
||||
export default function GlobalInfo({ countries }: GlobalInfoProps) {
|
||||
const t = useTranslations("Global");
|
||||
return (
|
||||
<section className="flex items-center justify-between">
|
||||
<GlobalBackButton />
|
||||
<p className="text-sm font-medium opacity-40">
|
||||
{t("Distributions")} {countries.length} {t("Regions")}
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
}
|
18
app/(main)/ClientComponents/GlobalLoading.tsx
Normal file
18
app/(main)/ClientComponents/GlobalLoading.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import GlobalBackButton from "@/components/GlobalBackButton";
|
||||
import { Loader } from "@/components/loading/Loader";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function GlobalLoading() {
|
||||
const t = useTranslations("Global");
|
||||
return (
|
||||
<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">
|
||||
{t("Loading")}
|
||||
<Loader visible={true} />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
@ -14,8 +14,8 @@ import { useEffect, useRef, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
export default function ServerListClient() {
|
||||
const { status, setStatus } = useStatus();
|
||||
const { filter, setFilter } = useFilter();
|
||||
const { status } = useStatus();
|
||||
const { filter } = useFilter();
|
||||
const t = useTranslations("ServerListClient");
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const defaultTag = "defaultTag";
|
||||
@ -23,13 +23,11 @@ export default function ServerListClient() {
|
||||
|
||||
const [tag, setTag] = useState<string>(defaultTag);
|
||||
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
useEffect(() => {
|
||||
const savedTag = sessionStorage.getItem("selectedTag") || defaultTag;
|
||||
setTag(savedTag);
|
||||
|
||||
restoreScrollPosition();
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
|
||||
const handleTagChange = (newTag: string) => {
|
||||
@ -71,7 +69,7 @@ export default function ServerListClient() {
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!data?.result || !isMounted) return null;
|
||||
if (!data?.result) return null;
|
||||
|
||||
const { result } = data;
|
||||
const sortedServers = result.sort((a, b) => {
|
||||
@ -121,8 +119,6 @@ export default function ServerListClient() {
|
||||
<section className="flex items-center gap-2 w-full overflow-hidden">
|
||||
<button
|
||||
onClick={() => {
|
||||
setStatus("all");
|
||||
setFilter(false);
|
||||
router.push(`/?global=true`);
|
||||
}}
|
||||
className="rounded-[50px] bg-stone-100 p-[10px] transition-all hover:bg-stone-200 dark:hover:bg-stone-700 dark:bg-stone-800"
|
||||
|
@ -43,12 +43,14 @@ export default function ServerOverviewClient() {
|
||||
<section className="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||
<Card
|
||||
onClick={() => {
|
||||
setFilter(false);
|
||||
if (!global) {
|
||||
setFilter(false);
|
||||
setStatus("all");
|
||||
}
|
||||
}}
|
||||
className="cursor-pointer hover:border-blue-500 transition-all"
|
||||
className={cn("cursor-pointer hover:border-blue-500 transition-all", {
|
||||
"pointer-events-none": global,
|
||||
})}
|
||||
>
|
||||
<CardContent className="px-6 py-3">
|
||||
<section className="flex flex-col gap-1">
|
||||
@ -74,8 +76,8 @@ export default function ServerOverviewClient() {
|
||||
</Card>
|
||||
<Card
|
||||
onClick={() => {
|
||||
setFilter(false);
|
||||
if (!global) {
|
||||
setFilter(false);
|
||||
setStatus("online");
|
||||
}
|
||||
}}
|
||||
@ -84,6 +86,9 @@ export default function ServerOverviewClient() {
|
||||
{
|
||||
"ring-green-500 ring-2 border-transparent": status === "online",
|
||||
},
|
||||
{
|
||||
"pointer-events-none": global,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<CardContent className="px-6 py-3">
|
||||
@ -111,8 +116,8 @@ export default function ServerOverviewClient() {
|
||||
</Card>
|
||||
<Card
|
||||
onClick={() => {
|
||||
setFilter(false);
|
||||
if (!global) {
|
||||
setFilter(false);
|
||||
setStatus("offline");
|
||||
}
|
||||
}}
|
||||
@ -121,6 +126,9 @@ export default function ServerOverviewClient() {
|
||||
{
|
||||
"ring-red-500 ring-2 border-transparent": status === "offline",
|
||||
},
|
||||
{
|
||||
"pointer-events-none": global,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<CardContent className="px-6 py-3">
|
||||
@ -148,8 +156,8 @@ export default function ServerOverviewClient() {
|
||||
</Card>
|
||||
<Card
|
||||
onClick={() => {
|
||||
setStatus("all");
|
||||
if (!global) {
|
||||
setStatus("all");
|
||||
setFilter(true);
|
||||
}
|
||||
}}
|
||||
@ -158,6 +166,9 @@ export default function ServerOverviewClient() {
|
||||
{
|
||||
"ring-purple-500 ring-2 border-transparent": filter === true,
|
||||
},
|
||||
{
|
||||
"pointer-events-none": global,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<CardContent className="relative px-6 py-3">
|
||||
|
@ -1,11 +1,9 @@
|
||||
import ServerList from "@/components/ServerList";
|
||||
import ServerOverview from "@/components/ServerOverview";
|
||||
import { Loader } from "@/components/loading/Loader";
|
||||
import { ServerStackIcon } from "@heroicons/react/20/solid";
|
||||
import Link from "next/link";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import ServerGlobal from "./ClientComponents/Global";
|
||||
import GlobalLoading from "./ClientComponents/GlobalLoading";
|
||||
|
||||
export default async function Home({
|
||||
searchParams,
|
||||
@ -18,22 +16,7 @@ export default async function Home({
|
||||
<ServerOverview />
|
||||
{!global && <ServerList />}
|
||||
{global && (
|
||||
<Suspense
|
||||
fallback={
|
||||
<section className="flex flex-col gap-4 mt-[3.2px]">
|
||||
<Link
|
||||
href={`/`}
|
||||
className="rounded-[50px] w-fit bg-stone-100 p-[10px] transition-all hover:bg-stone-200 dark:hover:bg-stone-700 dark:bg-stone-800"
|
||||
>
|
||||
<ServerStackIcon className="size-4" />
|
||||
</Link>
|
||||
<div className="flex min-h-40 flex-col items-center justify-center font-medium text-sm">
|
||||
Loading...
|
||||
<Loader visible={true} />
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
>
|
||||
<Suspense fallback={<GlobalLoading />}>
|
||||
<ServerGlobal />
|
||||
</Suspense>
|
||||
)}
|
||||
|
31
components/GlobalBackButton.tsx
Normal file
31
components/GlobalBackButton.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import { useFilter } from "@/lib/network-filter-context";
|
||||
import { useStatus } from "@/lib/status-context";
|
||||
import { ServerStackIcon } from "@heroicons/react/20/solid";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function GlobalBackButton() {
|
||||
const router = useRouter();
|
||||
const { setStatus } = useStatus();
|
||||
const { setFilter } = useFilter();
|
||||
|
||||
useEffect(() => {
|
||||
setStatus("all");
|
||||
setFilter(false);
|
||||
sessionStorage.removeItem("selectedTag");
|
||||
router.prefetch(`/`);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => {
|
||||
router.push(`/`);
|
||||
}}
|
||||
className="rounded-[50px] w-fit bg-stone-100 p-[10px] transition-all hover:bg-stone-200 dark:hover:bg-stone-700 dark:bg-stone-800"
|
||||
>
|
||||
<ServerStackIcon className="size-4" />
|
||||
</button>
|
||||
);
|
||||
}
|
@ -90,6 +90,11 @@
|
||||
"p_2277-2331_Overview": "👋 Overview",
|
||||
"p_2390-2457_wherethetimeis": "where the time is"
|
||||
},
|
||||
"Global": {
|
||||
"Loading": "Loading...",
|
||||
"Distributions": "Servers are distributed in",
|
||||
"Regions": "Regions"
|
||||
},
|
||||
"NotFoundPage": {
|
||||
"h1_490-590_404NotFound": "404 Not Found",
|
||||
"p_601-665_TARDISERROR": "TARDIS ERROR!",
|
||||
|
@ -90,6 +90,11 @@
|
||||
"p_2277-2331_Overview": "👋 概要",
|
||||
"p_2390-2457_wherethetimeis": "現在の時間"
|
||||
},
|
||||
"Global": {
|
||||
"Loading": "Loading...",
|
||||
"Distributions": "サーバーは",
|
||||
"Regions": "つの地域に分散されています"
|
||||
},
|
||||
"NotFoundPage": {
|
||||
"h1_490-590_404NotFound": "404 見つかりませんでした",
|
||||
"p_601-665_TARDISERROR": "ターディスエラー!",
|
||||
|
@ -90,6 +90,11 @@
|
||||
"p_2277-2331_Overview": "👋 概覽",
|
||||
"p_2390-2457_wherethetimeis": "當前時間"
|
||||
},
|
||||
"Global": {
|
||||
"Loading": "載入中...",
|
||||
"Distributions": "伺服器分佈在",
|
||||
"Regions": "個地區"
|
||||
},
|
||||
"NotFoundPage": {
|
||||
"h1_490-590_404NotFound": "404 未找到",
|
||||
"p_601-665_TARDISERROR": "TARDIS 錯誤!",
|
||||
|
@ -90,6 +90,11 @@
|
||||
"p_2277-2331_Overview": "👋 概览",
|
||||
"p_2390-2457_wherethetimeis": "当前时间"
|
||||
},
|
||||
"Global": {
|
||||
"Loading": "加载中...",
|
||||
"Distributions": "服务器分布在",
|
||||
"Regions": "个地区"
|
||||
},
|
||||
"NotFoundPage": {
|
||||
"h1_490-590_404NotFound": "404 未找到",
|
||||
"p_601-665_TARDISERROR": "TARDIS 错误!",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nezha-dash",
|
||||
"version": "1.4.3-fix",
|
||||
"version": "1.4.4-fix",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3040",
|
||||
|
Loading…
Reference in New Issue
Block a user