Merge remote-tracking branch 'origin/main' into cloudflare-dev

This commit is contained in:
hamster1963 2024-09-26 14:02:15 +08:00
commit c4c73c2ef6
43 changed files with 582 additions and 119 deletions

View File

@ -1,5 +1,6 @@
NezhaBaseUrl=http://1.1.1.1:8008 NezhaBaseUrl=http://1.1.1.1:8008
NezhaAuth=nezha-token NezhaAuth=nezha-token
ServerDisablePrefetch=false
NEXT_PUBLIC_NezhaFetchInterval=2000 NEXT_PUBLIC_NezhaFetchInterval=2000
NEXT_PUBLIC_ShowFlag=true NEXT_PUBLIC_ShowFlag=true
NEXT_PUBLIC_DisableCartoon=false NEXT_PUBLIC_DisableCartoon=false

View File

@ -17,7 +17,8 @@ COPY . .
RUN bun run build RUN bun run build
FROM base AS runner FROM node:21-alpine AS runner
WORKDIR /app WORKDIR /app
ENV NODE_ENV production ENV NODE_ENV production

View File

@ -5,13 +5,10 @@
</div> </div>
### 一键部署到 Vercel-推荐 | 一键部署到 Vercel-推荐 | Docker部署 | Cloudflare部署 |
| ----------------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------- |
[部署简易教程](https://buycoffee.top/blog/tech/nezha) | [部署简易教程](https://buycoffee.top/blog/tech/nezha) | [Docker 部署教程](https://buycoffee.top/blog/tech/nezha-docker) | [Cloudflare 部署教程](https://buycoffee.top/blog/tech/nezha-cloudflare) |
| [Vercel-demo](https://nezha-dash-ruddy.vercel.app) | [Docker-demo](https://nezha-docker.buycoffee.tech) | [Cloudflare-demo](https://nezha-cloudflare.buycoffee.tech) |
### Docker部署
[Docker 部署教程](https://buycoffee.top/blog/tech/nezha-docker)
#### 环境变量 #### 环境变量
@ -19,11 +16,19 @@
| ------------------------------ | -------------------- | -------------------------------- | | ------------------------------ | -------------------- | -------------------------------- |
| NezhaBaseUrl | nezha 面板地址 | http://120.x.x.x:8008 | | NezhaBaseUrl | nezha 面板地址 | http://120.x.x.x:8008 |
| NezhaAuth | nezha 面板 API Token | 5hAY3QX6Nl9B3Uxxxx26KMvOMyXS1Udi | | NezhaAuth | nezha 面板 API Token | 5hAY3QX6Nl9B3Uxxxx26KMvOMyXS1Udi |
| ServerDisablePrefetch | 是否禁用预加载 | **默认**false |
| NEXT_PUBLIC_NezhaFetchInterval | 获取数据间隔(毫秒) | **默认**2000 | | NEXT_PUBLIC_NezhaFetchInterval | 获取数据间隔(毫秒) | **默认**2000 |
| NEXT_PUBLIC_ShowFlag | 是否显示旗帜 | **默认**false | | NEXT_PUBLIC_ShowFlag | 是否显示旗帜 | **默认**false |
| NEXT_PUBLIC_DisableCartoon | 是否禁用卡通人物 | **默认**false | | NEXT_PUBLIC_DisableCartoon | 是否禁用卡通人物 | **默认**false |
<br> #### 多语言支持
| 语言 | 代码 | 是否完成翻译 |
| -------- | ---- | ------------ |
| 简体中文 | zh | 是 |
| 繁体中文 | zh-t | 是 |
| 英语 | en | 是 |
| 日语 | ja | 是 |
![screen-shot-one](/.github/shotOne.png) ![screen-shot-one](/.github/shotOne.png)
![screen-shot-two](/.github/shotTwo.png) ![screen-shot-two](/.github/shotTwo.png)

View File

@ -1,13 +0,0 @@
import ServerList from "@/components/ServerList";
import ServerOverview from "@/components/ServerOverview";
export const runtime = 'edge';
export default function Home() {
return (
<div className="mx-auto grid w-full max-w-5xl gap-4 md:gap-6">
<ServerOverview />
<ServerList />
</div>
);
}

View File

@ -1,18 +1,15 @@
"use client"; "use client";
import { ServerApi } from "@/app/types/nezha-api"; import { ServerApi } from "../../types/nezha-api";
import ServerCard from "@/components/ServerCard"; import ServerCard from "../../../../components/ServerCard";
import { nezhaFetcher } from "@/lib/utils"; import { nezhaFetcher } from "../../../../lib/utils";
import useSWR from "swr"; import useSWR from "swr";
import getEnv from "@/lib/env-entry"; import getEnv from "../../../../lib/env-entry";
export default function ServerListClient() { export default function ServerListClient() {
const { data } = useSWR<ServerApi>("/api/server", nezhaFetcher, { const { data } = useSWR<ServerApi>("/api/server", nezhaFetcher, {
refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000, refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000,
}); });
if (!data) return null; if (!data) return null;
const sortedServers = data.result.sort((a, b) => { const sortedServers = data.result.sort((a, b) => {
if (a.display_index && b.display_index) { if (a.display_index && b.display_index) {
return b.display_index - a.display_index; return b.display_index - a.display_index;
@ -21,7 +18,6 @@ export default function ServerListClient() {
if (b.display_index) return 1; if (b.display_index) return 1;
return a.id - b.id; return a.id - b.id;
}); });
return ( return (
<section className="grid grid-cols-1 gap-2 md:grid-cols-2"> <section className="grid grid-cols-1 gap-2 md:grid-cols-2">
{sortedServers.map((serverInfo) => ( {sortedServers.map((serverInfo) => (

View File

@ -1,25 +1,26 @@
"use client"; "use client";
import { Card, CardContent } from "@/components/ui/card"; import { useTranslations } from "next-intl";
import blogMan from "@/public/blog-man.webp"; import { Card, CardContent } from "../../../../components/ui/card";
import blogMan from "../../../../public/blog-man.webp";
import Image from "next/image"; import Image from "next/image";
import useSWR from "swr"; import useSWR from "swr";
import { formatBytes, nezhaFetcher } from "@/lib/utils"; import { formatBytes, nezhaFetcher } from "../../../../lib/utils";
import { Loader } from "@/components/loading/Loader"; import { Loader } from "../../../../components/loading/Loader";
import { ServerApi } from "@/app/types/nezha-api"; import { ServerApi } from "../../types/nezha-api";
import getEnv from "@/lib/env-entry"; import getEnv from "../../../../lib/env-entry";
export default function ServerOverviewClient() { export default function ServerOverviewClient() {
const t = useTranslations("ServerOverviewClient");
const { data } = useSWR<ServerApi>("/api/server", nezhaFetcher); const { data } = useSWR<ServerApi>("/api/server", nezhaFetcher);
const disableCartoon = getEnv("NEXT_PUBLIC_DisableCartoon") === "true"; const disableCartoon = getEnv("NEXT_PUBLIC_DisableCartoon") === "true";
return ( return (
<section className="grid grid-cols-2 gap-4 md:grid-cols-4"> <section className="grid grid-cols-2 gap-4 md:grid-cols-4">
<Card> <Card>
<CardContent className="px-6 py-3"> <CardContent className="px-6 py-3">
<section className="flex flex-col gap-1"> <section className="flex flex-col gap-1">
<p className="text-sm md:text-base font-medium">Total servers</p> <p className="text-sm font-medium md:text-base">
{t("p_816-881_Totalservers")}
</p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="relative flex h-2 w-2"> <span className="relative flex h-2 w-2">
<span className="relative inline-flex h-2 w-2 rounded-full bg-blue-500"></span> <span className="relative inline-flex h-2 w-2 rounded-full bg-blue-500"></span>
@ -40,7 +41,9 @@ export default function ServerOverviewClient() {
<Card> <Card>
<CardContent className="px-6 py-3"> <CardContent className="px-6 py-3">
<section className="flex flex-col gap-1"> <section className="flex flex-col gap-1">
<p className="text-sm md:text-base font-medium">Online servers</p> <p className="text-sm font-medium md:text-base">
{t("p_1610-1676_Onlineservers")}
</p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="relative flex h-2 w-2"> <span className="relative flex h-2 w-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-500 opacity-75"></span> <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-500 opacity-75"></span>
@ -62,7 +65,9 @@ export default function ServerOverviewClient() {
<Card> <Card>
<CardContent className="px-6 py-3"> <CardContent className="px-6 py-3">
<section className="flex flex-col gap-1"> <section className="flex flex-col gap-1">
<p className="text-sm md:text-base font-medium">Offline servers</p> <p className="text-sm font-medium md:text-base">
{t("p_2532-2599_Offlineservers")}
</p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="relative flex h-2 w-2"> <span className="relative flex h-2 w-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-red-500 opacity-75"></span> <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-red-500 opacity-75"></span>
@ -84,7 +89,9 @@ export default function ServerOverviewClient() {
<Card> <Card>
<CardContent className="relative px-6 py-3"> <CardContent className="relative px-6 py-3">
<section className="flex flex-col gap-1"> <section className="flex flex-col gap-1">
<p className="text-sm md:text-base font-medium">Total bandwidth</p> <p className="text-sm font-medium md:text-base">
{t("p_3463-3530_Totalbandwidth")}
</p>
{data ? ( {data ? (
<p className="text-lg font-semibold"> <p className="text-lg font-semibold">
{formatBytes(data?.total_bandwidth)} {formatBytes(data?.total_bandwidth)}
@ -97,7 +104,7 @@ export default function ServerOverviewClient() {
</section> </section>
{!disableCartoon && ( {!disableCartoon && (
<Image <Image
className="pointer-events-none absolute right-3 top-[-85px] z-10 w-20" className="pointer-events-none absolute right-3 top-[-85px] z-10 w-20 scale-90 md:scale-100"
alt={"Hamster1963"} alt={"Hamster1963"}
src={blogMan} src={blogMan}
priority priority

View File

@ -1,20 +1,23 @@
import { useTranslations } from "next-intl";
export default function Footer() { export default function Footer() {
const t = useTranslations("Footer");
return ( return (
<footer className="mx-auto w-full max-w-5xl"> <footer className="mx-auto w-full max-w-5xl">
<section className="flex flex-col"> <section className="flex flex-col">
<p className="mt-3 flex gap-1 text-[13px] font-light tracking-tight text-neutral-600/50 dark:text-neutral-300/50"> <p className="mt-3 flex gap-1 text-[13px] font-light tracking-tight text-neutral-600/50 dark:text-neutral-300/50">
Find the code on{" "} {t("p_146-598_Findthecodeon")}{" "}
<a <a
href="https://github.com/hamster1963/nezha-dash" href="https://github.com/hamster1963/nezha-dash"
target="_blank" target="_blank"
className="cursor-pointer font-normal underline decoration-yellow-500 decoration-2 underline-offset-2 dark:decoration-yellow-500/50" className="cursor-pointer font-normal underline decoration-yellow-500 decoration-2 underline-offset-2 dark:decoration-yellow-500/50"
> >
GitHub {t("a_303-585_GitHub")}
</a> </a>
</p> </p>
<section className="mt-1 flex items-center gap-2 text-[13px] font-light tracking-tight text-neutral-600/50 dark:text-neutral-300/50"> <section className="mt-1 flex items-center gap-2 text-[13px] font-light tracking-tight text-neutral-600/50 dark:text-neutral-300/50">
© 2020-{new Date().getFullYear()}{" "} {t("section_607-869_2020")}
<a href={"https://buycoffee.top"}>@Hamster1963</a> {new Date().getFullYear()}{" "}
<a href={"https://buycoffee.top"}>{t("a_800-850_Hamster1963")}</a>
</section> </section>
</section> </section>
</footer> </footer>

View File

@ -1,16 +1,18 @@
"use client"; "use client";
import { useTranslations } from "next-intl";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import Image from "next/image"; import Image from "next/image";
import { Separator } from "@/components/ui/separator"; import { Separator } from "../../../components/ui/separator";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { ModeToggle } from "@/components/ThemeSwitcher"; import { ModeToggle } from "../../../components/ThemeSwitcher";
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
function Header() { function Header() {
const t = useTranslations("Header");
return ( return (
<div className="mx-auto w-full max-w-5xl"> <div className="mx-auto w-full max-w-5xl">
<section className="flex items-center justify-between"> <section className="flex items-center justify-between">
<section className="text-base flex items-center font-medium"> <section className="flex items-center text-base font-medium">
<div className="mr-1 flex flex-row items-center justify-start"> <div className="mr-1 flex flex-row items-center justify-start">
<Image <Image
width={40} width={40}
@ -27,10 +29,13 @@ function Header() {
className="mx-2 hidden h-4 w-[1px] md:block" className="mx-2 hidden h-4 w-[1px] md:block"
/> />
<p className="hidden text-sm font-medium opacity-40 md:block"> <p className="hidden text-sm font-medium opacity-40 md:block">
Simple and beautiful dashboard {t("p_1079-1199_Simpleandbeautifuldashbo")}
</p> </p>
</section> </section>
<ModeToggle /> <section className="flex items-center gap-2">
<LanguageSwitcher />
<ModeToggle />
</section>
</section> </section>
<Overview /> <Overview />
</div> </div>
@ -40,22 +45,19 @@ function Header() {
// https://github.com/streamich/react-use/blob/master/src/useInterval.ts // https://github.com/streamich/react-use/blob/master/src/useInterval.ts
const useInterval = (callback: Function, delay?: number | null) => { const useInterval = (callback: Function, delay?: number | null) => {
const savedCallback = useRef<Function>(() => { }); const savedCallback = useRef<Function>(() => { });
useEffect(() => { useEffect(() => {
savedCallback.current = callback; savedCallback.current = callback;
}); });
useEffect(() => { useEffect(() => {
if (delay !== null) { if (delay !== null) {
const interval = setInterval(() => savedCallback.current(), delay || 0); const interval = setInterval(() => savedCallback.current(), delay || 0);
return () => clearInterval(interval); return () => clearInterval(interval);
} }
return undefined; return undefined;
}, [delay]); }, [delay]);
}; };
function Overview() { function Overview() {
const t = useTranslations("Overview");
const [mouted, setMounted] = useState(false); const [mouted, setMounted] = useState(false);
useEffect(() => { useEffect(() => {
setMounted(true); setMounted(true);
@ -65,16 +67,16 @@ function Overview() {
const [timeString, setTimeString] = useState( const [timeString, setTimeString] = useState(
DateTime.now().setLocale("en-US").toLocaleString(timeOption), DateTime.now().setLocale("en-US").toLocaleString(timeOption),
); );
useInterval(() => { useInterval(() => {
setTimeString(DateTime.now().setLocale("en-US").toLocaleString(timeOption)); setTimeString(DateTime.now().setLocale("en-US").toLocaleString(timeOption));
}, 1000); }, 1000);
return ( return (
<section className={"mt-10 flex flex-col md:mt-16"}> <section className={"mt-10 flex flex-col md:mt-16"}>
<p className="text-base font-semibold">👋 Overview</p> <p className="text-base font-semibold">{t("p_2277-2331_Overview")}</p>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<p className="text-sm font-medium opacity-50">where the time is</p> <p className="text-sm font-medium opacity-50">
{t("p_2390-2457_wherethetimeis")}
</p>
{mouted && ( {mouted && (
<p className="opacity-1 text-sm font-medium">{timeString}</p> <p className="opacity-1 text-sm font-medium">{timeString}</p>
)} )}
@ -82,5 +84,4 @@ function Overview() {
</section> </section>
); );
} }
export default Header; export default Header;

View File

@ -1,12 +1,10 @@
import React from "react"; import React from "react";
import Header from "@/app/[locale]/(main)/header";
import Header from "@/app/(main)/header";
import Footer from "./footer"; import Footer from "./footer";
type DashboardProps = { type DashboardProps = {
children: React.ReactNode; children: React.ReactNode;
}; };
export default function MainLayout({ children }: DashboardProps) { export default function MainLayout({ children }: DashboardProps) {
return ( return (
<div className="flex min-h-screen w-full flex-col"> <div className="flex min-h-screen w-full flex-col">

View File

@ -0,0 +1,31 @@
import ServerList from "../../../components/ServerList";
import ServerOverview from "../../../components/ServerOverview";
import getEnv from "../../../lib/env-entry";
import { GetNezhaData } from "../../../lib/serverFetch";
import { SWRConfig } from "swr";
export const runtime = 'edge';
const disablePrefetch = getEnv("ServerDisablePrefetch") === "true";
const fallback = disablePrefetch
? {}
: {
"/api/server": GetNezhaData(),
};
export default function Home() {
return (
<SWRConfig
value={{
fallback: fallback,
}}
>
<div className="mx-auto grid w-full max-w-5xl gap-4 md:gap-6">
<ServerOverview />
<ServerList />
</div>
</SWRConfig>
);
}

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 263 B

After

Width:  |  Height:  |  Size: 263 B

View File

Before

Width:  |  Height:  |  Size: 324 B

After

Width:  |  Height:  |  Size: 324 B

View File

@ -1,12 +1,14 @@
import "@/styles/globals.css"; // @auto-i18n-check. Please do not delete the line.
import "@/styles/globals.css";
import React from "react";
import { NextIntlClientProvider, useMessages } from "next-intl";
import { PublicEnvScript } from "next-runtime-env";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Inter as FontSans } from "next/font/google"; import { Inter as FontSans } from "next/font/google";
import { ThemeProvider } from "next-themes"; import { ThemeProvider } from "next-themes";
import React from "react";
import { Viewport } from "next"; import { Viewport } from "next";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { PublicEnvScript } from "next-runtime-env";
const fontSans = FontSans({ const fontSans = FontSans({
subsets: ["latin"], subsets: ["latin"],
@ -31,13 +33,16 @@ export const viewport: Viewport = {
userScalable: false, userScalable: false,
}; };
interface RootLayoutProps { export default function LocaleLayout({
children,
params: { locale },
}: {
children: React.ReactNode; children: React.ReactNode;
} params: { locale: string };
}) {
export default function RootLayout({ children }: RootLayoutProps) { const messages = useMessages();
return ( return (
<html lang="en" suppressHydrationWarning> <html lang={locale} suppressHydrationWarning>
<head> <head>
<PublicEnvScript /> <PublicEnvScript />
</head> </head>
@ -53,7 +58,9 @@ export default function RootLayout({ children }: RootLayoutProps) {
enableSystem enableSystem
disableTransitionOnChange disableTransitionOnChange
> >
{children} <NextIntlClientProvider locale={locale} messages={messages}>
{children}
</NextIntlClientProvider>
</ThemeProvider> </ThemeProvider>
</body> </body>
</html> </html>

View File

@ -1,9 +1,11 @@
import { useTranslations } from "next-intl";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
export const runtime = 'edge'; export const runtime = 'edge';
export default function NotFoundPage() { export default function NotFoundPage() {
const t = useTranslations("NotFoundPage");
return ( return (
<main className="relative h-screen w-full"> <main className="relative h-screen w-full">
<div className="absolute inset-0 m-4 flex items-center justify-center"> <div className="absolute inset-0 m-4 flex items-center justify-center">
@ -16,11 +18,13 @@ export default function NotFoundPage() {
/> />
<div className="text-container absolute right-4 p-4 md:right-20"> <div className="text-container absolute right-4 p-4 md:right-20">
<h1 className="text-2xl font-bold opacity-80 md:text-5xl"> <h1 className="text-2xl font-bold opacity-80 md:text-5xl">
404 Not Found {t("h1_490-590_404NotFound")}
</h1> </h1>
<p className="text-lg opacity-60 md:text-base">TARDIS ERROR!</p> <p className="text-lg opacity-60 md:text-base">
{t("p_601-665_TARDISERROR")}
</p>
<Link href={"/"} className="text-2xl opacity-80 md:text-3xl"> <Link href={"/"} className="text-2xl opacity-80 md:text-3xl">
Doctor? {t("Link_676-775_Doctor")}
</Link> </Link>
</div> </div>
</div> </div>

View File

@ -1,5 +1,6 @@
import { NezhaAPI, ServerApi } from "@/app/types/nezha-api";
import { MakeOptional } from "@/app/types/utils"; import { NezhaAPI, ServerApi } from "@/app/[locale]/types/nezha-api";
import { MakeOptional } from "@/app/[locale]/types/utils";
import getEnv from "@/lib/env-entry"; import getEnv from "@/lib/env-entry";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";

10
auto-i18n-config.json Normal file
View File

@ -0,0 +1,10 @@
{
"defaultLang": "en",
"translatorServerName": "azure",
"needLangs": ["en", "zh", "zh-t", "ja"],
"brandWords": [],
"unMoveToLocaleDirFiles": [],
"enableStaticRendering": false,
"enableSubPageRedirectToLocale": false,
"disableDefaultLangRedirect": true
}

BIN
bun.lockb

Binary file not shown.

View File

@ -0,0 +1,70 @@
"use client";
import { useLocale } from "next-intl";
import { localeItems } from "../i18n-metadata";
import { useRouter, usePathname } from "next/navigation";
import * as React from "react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export function LanguageSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const handleChange = (code: string) => {
const newLocale = code;
const rootPath = "/";
const currentLocalePath = `/${locale}`;
const newLocalePath = `/${newLocale}`;
// Function to construct new path with locale prefix
const constructLocalePath = (path: string, newLocale: string) => {
if (path.startsWith(currentLocalePath)) {
return path.replace(currentLocalePath, `/${newLocale}`);
} else {
return `/${newLocale}${path}`;
}
};
if (pathname === rootPath || !pathname) {
router.push(newLocalePath);
} else if (
pathname === currentLocalePath ||
pathname === `${currentLocalePath}/`
) {
router.push(newLocalePath);
} else {
const newPath = constructLocalePath(pathname, newLocale);
router.push(newPath);
}
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="rounded-full px-[9px]">
{localeItems.find((item) => item.code === locale)?.name}
<span className="sr-only">Change language</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{localeItems.map((item) => (
<DropdownMenuItem
key={item.code}
onClick={() => handleChange(item.code)}
>
{item.name}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@ -1,4 +1,5 @@
import { NezhaAPISafe } from "@/app/types/nezha-api"; import { useTranslations } from "next-intl";
import { NezhaAPISafe } from "../app/[locale]/types/nezha-api";
import ServerUsageBar from "@/components/ServerUsageBar"; import ServerUsageBar from "@/components/ServerUsageBar";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
import { import {
@ -16,6 +17,7 @@ export default function ServerCard({
}: { }: {
serverInfo: NezhaAPISafe; serverInfo: NezhaAPISafe;
}) { }) {
const t = useTranslations("ServerCard");
const { name, country_code, online, cpu, up, down, mem, stg, ...props } = const { name, country_code, online, cpu, up, down, mem, stg, ...props } =
formatNezhaInfo(serverInfo); formatNezhaInfo(serverInfo);
@ -55,36 +57,46 @@ export default function ServerCard({
</PopoverContent> </PopoverContent>
</Popover> </Popover>
<section className={"grid grid-cols-5 items-center gap-3"}> <section className={"grid grid-cols-5 items-center gap-3"}>
<div className={"flex flex-col"}> <div className={"flex w-14 flex-col"}>
<p className="text-xs text-muted-foreground">CPU</p> {" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("CPU")}</p>
<div className="flex items-center text-xs font-semibold"> <div className="flex items-center text-xs font-semibold">
{cpu.toFixed(2)}% {cpu.toFixed(2)}%
</div> </div>
<ServerUsageBar value={cpu} /> <ServerUsageBar value={cpu} />
</div> </div>
<div className={"flex flex-col"}> <div className={"flex w-14 flex-col"}>
<p className="text-xs text-muted-foreground">Mem</p> {" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("Mem")}</p>
<div className="flex items-center text-xs font-semibold"> <div className="flex items-center text-xs font-semibold">
{mem.toFixed(2)}% {mem.toFixed(2)}%
</div> </div>
<ServerUsageBar value={mem} /> <ServerUsageBar value={mem} />
</div> </div>
<div className={"flex flex-col"}> <div className={"flex w-14 flex-col"}>
<p className="text-xs text-muted-foreground">STG</p> {" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("STG")}</p>
<div className="flex items-center text-xs font-semibold"> <div className="flex items-center text-xs font-semibold">
{stg.toFixed(2)}% {stg.toFixed(2)}%
</div> </div>
<ServerUsageBar value={stg} /> <ServerUsageBar value={stg} />
</div> </div>
<div className={"flex flex-col"}> <div className={"flex w-14 flex-col"}>
<p className="text-xs text-muted-foreground">Upload</p> {" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("Upload")}</p>
<div className="flex items-center text-xs font-semibold"> <div className="flex items-center text-xs font-semibold">
{up.toFixed(2)} {up.toFixed(2)}
Mb/s Mb/s
</div> </div>
</div> </div>
<div className={"flex flex-col"}> <div className={"flex w-14 flex-col"}>
<p className="text-xs text-muted-foreground">Download</p> {" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("Download")}</p>
<div className="flex items-center text-xs font-semibold"> <div className="flex items-center text-xs font-semibold">
{down.toFixed(2)} {down.toFixed(2)}
Mb/s Mb/s
@ -122,7 +134,7 @@ export default function ServerCard({
</section> </section>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-fit p-2" side="top"> <PopoverContent className="w-fit p-2" side="top">
<p className="text-sm text-muted-foreground">Offline</p> <p className="text-sm text-muted-foreground">{t("Offline")}</p>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</Card> </Card>

View File

@ -1,4 +1,5 @@
import { NezhaAPISafe } from "@/app/types/nezha-api"; import { useTranslations } from "next-intl";
import { NezhaAPISafe } from "../app/[locale]/types/nezha-api";
import { cn, formatBytes } from "@/lib/utils"; import { cn, formatBytes } from "@/lib/utils";
export function ServerCardPopoverCard({ export function ServerCardPopoverCard({
@ -31,39 +32,40 @@ export default function ServerCardPopover({
host: NezhaAPISafe["host"]; host: NezhaAPISafe["host"];
status: NezhaAPISafe["status"]; status: NezhaAPISafe["status"];
}) { }) {
const t = useTranslations("ServerCardPopover");
return ( return (
<section className="max-w-[300px]"> <section className="max-w-[300px]">
<ServerCardPopoverCard <ServerCardPopoverCard
title="System" title={t("System")}
content={`${host.Platform}-${host.PlatformVersion} [${host.Virtualization}: ${host.Arch}]`} content={`${host.Platform}-${host.PlatformVersion} [${host.Virtualization}: ${host.Arch}]`}
/> />
<ServerCardPopoverCard <ServerCardPopoverCard
title="CPU" title={t("CPU")}
content={`${host.CPU.map((item) => item).join(", ")}`} content={`${host.CPU.map((item) => item).join(", ")}`}
/> />
<ServerCardPopoverCard <ServerCardPopoverCard
title="Mem" title={t("Mem")}
content={`${formatBytes(status.MemUsed)} / ${formatBytes(host.MemTotal)}`} content={`${formatBytes(status.MemUsed)} / ${formatBytes(host.MemTotal)}`}
/> />
<ServerCardPopoverCard <ServerCardPopoverCard
title="STG" title={t("STG")}
content={`${formatBytes(status.DiskUsed)} / ${formatBytes(host.DiskTotal)}`} content={`${formatBytes(status.DiskUsed)} / ${formatBytes(host.DiskTotal)}`}
/> />
<ServerCardPopoverCard <ServerCardPopoverCard
title="Swap" title={t("Swap")}
content={`${formatBytes(status.SwapUsed)} / ${formatBytes(host.SwapTotal)}`} content={`${formatBytes(status.SwapUsed)} / ${formatBytes(host.SwapTotal)}`}
/> />
<ServerCardPopoverCard <ServerCardPopoverCard
title="Network" title={t("Network")}
content={`${formatBytes(status.NetOutTransfer)} / ${formatBytes(status.NetInTransfer)}`} content={`${formatBytes(status.NetOutTransfer)} / ${formatBytes(status.NetInTransfer)}`}
/> />
<ServerCardPopoverCard <ServerCardPopoverCard
title="Load" title={t("Load")}
content={`${status.Load1.toFixed(2)} / ${status.Load5.toFixed(2)} / ${status.Load15.toFixed(2)}`} content={`${status.Load1.toFixed(2)} / ${status.Load5.toFixed(2)} / ${status.Load15.toFixed(2)}`}
/> />
<ServerCardPopoverCard <ServerCardPopoverCard
className="mb-0" className="mb-0"
title="Online" title={t("Online")}
content={`${(status.Uptime / 86400).toFixed(0)} Days`} content={`${(status.Uptime / 86400).toFixed(0)} Days`}
/> />
</section> </section>

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import ServerListClient from "@/app/(main)/ClientComponents/ServerListClient"; import ServerListClient from "../app/[locale]/(main)/ClientComponents/ServerListClient";
export default async function ServerList() { export default async function ServerList() {
return <ServerListClient />; return <ServerListClient />;

View File

@ -1,4 +1,4 @@
import ServerOverviewClient from "@/app/(main)/ClientComponents/ServerOverviewClient"; import ServerOverviewClient from "../app/[locale]/(main)/ClientComponents/ServerOverviewClient";
export default async function ServerOverview() { export default async function ServerOverview() {
return <ServerOverviewClient />; return <ServerOverviewClient />;

View File

@ -1,5 +1,6 @@
"use client"; "use client";
import { useTranslations } from "next-intl";
import { Moon, Sun } from "lucide-react"; import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import * as React from "react"; import * as React from "react";
@ -14,7 +15,7 @@ import {
export function ModeToggle() { export function ModeToggle() {
const { setTheme } = useTheme(); const { setTheme } = useTheme();
const t = useTranslations("ThemeSwitcher");
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@ -26,13 +27,13 @@ export function ModeToggle() {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}> <DropdownMenuItem onClick={() => setTheme("light")}>
Light {t("Light")}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}> <DropdownMenuItem onClick={() => setTheme("dark")}>
Dark {t("Dark")}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}> <DropdownMenuItem onClick={() => setTheme("system")}>
System {t("System")}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>

View File

@ -1,5 +1,6 @@
NezhaBaseUrl=http://0.0.0.0:8008 NezhaBaseUrl=http://0.0.0.0:8008
NezhaAuth=5hAY3QX6Nl9B3UOQgB26KdsdS1dsdUdM NezhaAuth=5hAY3QX6Nl9B3UOQgB26KdsdS1dsdUdM
ServerDisablePrefetch=false
NEXT_PUBLIC_NezhaFetchInterval=5000 NEXT_PUBLIC_NezhaFetchInterval=5000
NEXT_PUBLIC_ShowFlag=true NEXT_PUBLIC_ShowFlag=true
NEXT_PUBLIC_DisableCartoon=true NEXT_PUBLIC_DisableCartoon=true

View File

@ -1,3 +1,4 @@
version: "3"
services: services:
nezha-dash: nezha-dash:
container_name: nezha-dash container_name: nezha-dash

View File

@ -1,3 +1,4 @@
version: "3"
services: services:
nezha-dash: nezha-dash:
container_name: nezha-dash container_name: nezha-dash

24
i18n-metadata.ts Normal file
View File

@ -0,0 +1,24 @@
// @auto-i18n-check. Please do not delete the line.
export const localeItems = [
{ code: "en", name: "English" },
{ code: "ja", name: "日本語" },
{ code: "zh-t", name: "中文繁體" },
{ code: "zh", name: "中文简体" },
//{code: 'ar', name: 'العربية'},
//{code: 'de', name: 'Deutsch'},
//{code: 'es', name: 'Español'},
//{code: 'fr', name: 'Français'},
//{code: 'hi', name: 'हिन्दी'},
//{code: 'id', name: 'Bahasa Indonesia'},
//{code: 'it', name: 'Italiano'},
//{code: 'ko', name: '한국어'},
//{code: 'ms', name: 'Bahasa Melayu'},
//{code: 'pt', name: 'Português'},
//{code: 'ru', name: 'Русский'},
//{code: 'th', name: 'ไทย'},
//{code: 'vi', name: 'Tiếng Việt'},
];
export const locales = localeItems.map((item) => item.code);
export const defaultLocale = "en";

14
i18n.ts Normal file
View File

@ -0,0 +1,14 @@
// @auto-i18n-check. Please do not delete the line.
import { notFound } from "next/navigation";
import { getRequestConfig } from "next-intl/server";
import { locales } from "./i18n-metadata";
export default getRequestConfig(async ({ locale }) => {
// Validate that the incoming `locale` parameter is valid
if (!locales.includes(locale as any)) notFound();
return {
messages: (await import(`./messages/${locale}.json`)).default,
};
});

61
lib/serverFetch.tsx Normal file
View File

@ -0,0 +1,61 @@
"use server";
import { NezhaAPI, ServerApi } from "../app/[locale]/types/nezha-api";
import { MakeOptional } from "../app/[locale]/types/utils";
import { unstable_noStore as noStore } from "next/cache";
import getEnv from "./env-entry";
export async function GetNezhaData() {
noStore();
var nezhaBaseUrl = getEnv("NezhaBaseUrl");
if (!nezhaBaseUrl) {
console.error("NezhaBaseUrl is not set");
throw new Error("NezhaBaseUrl is not set");
}
// Remove trailing slash
if (nezhaBaseUrl[nezhaBaseUrl.length - 1] === "/") {
nezhaBaseUrl = nezhaBaseUrl.slice(0, -1);
}
try {
const response = await fetch(nezhaBaseUrl + "/api/v1/server/details", {
headers: {
Authorization: getEnv("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<NezhaAPI, "ipv4" | "ipv6" | "valid_ip">) => {
if (timestamp - element.last_active > 300) {
data.offline_servers += 1;
element.online_status = false;
} else {
data.live_servers += 1;
element.online_status = true;
}
data.total_bandwidth += element.status.NetOutTransfer;
delete element.ipv4;
delete element.ipv6;
delete element.valid_ip;
return element;
},
);
return data;
} catch (error) {
return error;
}
}

View File

@ -1,4 +1,4 @@
import { NezhaAPISafe } from "@/app/types/nezha-api"; import { NezhaAPISafe } from "../app/[locale]/types/nezha-api";
import { type ClassValue, clsx } from "clsx"; import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";

50
messages/en.json Normal file
View File

@ -0,0 +1,50 @@
{
"ServerOverviewClient": {
"p_816-881_Totalservers": "Total servers",
"p_1610-1676_Onlineservers": "Online servers",
"p_2532-2599_Offlineservers": "Offline servers",
"p_3463-3530_Totalbandwidth": "Total bandwidth"
},
"ServerCard": {
"CPU": "CPU",
"Mem": "Mem",
"STG": "STG",
"Upload": "Upload",
"Download": "Download",
"Offline": "Offline"
},
"ServerCardPopover": {
"System": "System",
"CPU": "CPU",
"Mem": "Mem",
"STG": "STG",
"Swap": "Swap",
"Network": "Network",
"Load": "Load",
"Online": "Online",
"Offline": "Offline"
},
"ThemeSwitcher": {
"Light": "Light",
"Dark": "Dark",
"System": "System"
},
"Footer": {
"p_146-598_Findthecodeon": "Find the code on",
"a_303-585_GitHub": "GitHub",
"section_607-869_2020": "© 2020-",
"a_800-850_Hamster1963": "@Hamster1963"
},
"Header": {
"p_1079-1199_Simpleandbeautifuldashbo": "Simple and beautiful dashboard"
},
"Overview": {
"p_2277-2331_Overview": "👋 Overview",
"p_2390-2457_wherethetimeis": "where the time is"
},
"NotFoundPage": {
"h1_490-590_404NotFound": "404 Not Found",
"p_601-665_TARDISERROR": "TARDIS ERROR!",
"Link_676-775_Doctor": "Doctor?"
}
}

50
messages/ja.json Normal file
View File

@ -0,0 +1,50 @@
{
"ServerOverviewClient": {
"p_816-881_Totalservers": "サーバーの総数",
"p_1610-1676_Onlineservers": "オンラインサーバー",
"p_2532-2599_Offlineservers": "オフラインサーバー",
"p_3463-3530_Totalbandwidth": "総流量"
},
"ServerCard": {
"CPU": "CPU",
"Mem": "Mem",
"STG": "STG",
"Upload": "Upload",
"Download": "Download",
"Offline": "Offline"
},
"ServerCardPopover": {
"System": "システム",
"CPU": "CPU",
"Mem": "メモリ",
"STG": "ストレージ",
"Swap": "スワップ",
"Network": "ネットワーク",
"Load": "負荷",
"Online": "オンライン時間",
"Offline": "オフライン"
},
"ThemeSwitcher": {
"Light": "ライト",
"Dark": "ダーク",
"System": "システム"
},
"Footer": {
"p_146-598_Findthecodeon": "コードのオープンソース",
"a_303-585_GitHub": "GitHub",
"section_607-869_2020": "© 2020〜",
"a_800-850_Hamster1963": "@Hamster1963"
},
"Header": {
"p_1079-1199_Simpleandbeautifuldashbo": "シンプルで美しいダッシュボード"
},
"Overview": {
"p_2277-2331_Overview": "👋 概要",
"p_2390-2457_wherethetimeis": "現在の時間"
},
"NotFoundPage": {
"h1_490-590_404NotFound": "404 見つかりませんでした",
"p_601-665_TARDISERROR": "ターディスエラー!",
"Link_676-775_Doctor": "Doctor?"
}
}

50
messages/zh-t.json Normal file
View File

@ -0,0 +1,50 @@
{
"ServerOverviewClient": {
"p_816-881_Totalservers": "伺服器總數",
"p_1610-1676_Onlineservers": "在線伺服器",
"p_2532-2599_Offlineservers": "離線伺服器",
"p_3463-3530_Totalbandwidth": "總流量"
},
"ServerCard": {
"CPU": "CPU",
"Mem": "記憶體",
"STG": "儲存",
"Upload": "上傳",
"Download": "下載",
"Offline": "離線"
},
"ServerCardPopover": {
"System": "系統",
"CPU": "CPU",
"Mem": "記憶體",
"STG": "儲存",
"Swap": "虛擬記憶體",
"Network": "網路",
"Load": "負載",
"Online": "在線時間",
"Offline": "離線"
},
"ThemeSwitcher": {
"Light": "亮色",
"Dark": "暗色",
"System": "系統"
},
"Footer": {
"p_146-598_Findthecodeon": "程式碼開源",
"a_303-585_GitHub": "GitHub",
"section_607-869_2020": "© 2020-",
"a_800-850_Hamster1963": "@Hamster1963"
},
"Header": {
"p_1079-1199_Simpleandbeautifuldashbo": "簡單美觀的儀錶板"
},
"Overview": {
"p_2277-2331_Overview": "👋 概覽",
"p_2390-2457_wherethetimeis": "當前時間"
},
"NotFoundPage": {
"h1_490-590_404NotFound": "404 未找到",
"p_601-665_TARDISERROR": "TARDIS 錯誤!",
"Link_676-775_Doctor": "Doctor"
}
}

50
messages/zh.json Normal file
View File

@ -0,0 +1,50 @@
{
"ServerOverviewClient": {
"p_816-881_Totalservers": "服务器总数",
"p_1610-1676_Onlineservers": "在线服务器",
"p_2532-2599_Offlineservers": "离线服务器",
"p_3463-3530_Totalbandwidth": "总流量"
},
"ServerCard": {
"CPU": "CPU",
"Mem": "内存",
"STG": "存储",
"Upload": "上传",
"Download": "下载",
"Offline": "离线"
},
"ServerCardPopover": {
"System": "系统",
"CPU": "CPU",
"Mem": "内存",
"STG": "存储",
"Swap": "虚拟内存",
"Network": "网络",
"Load": "负载",
"Online": "在线时间",
"Offline": "离线"
},
"ThemeSwitcher": {
"Light": "亮色",
"Dark": "暗色",
"System": "系统"
},
"Footer": {
"p_146-598_Findthecodeon": "代码开源在",
"a_303-585_GitHub": "GitHub",
"section_607-869_2020": "© 2020-",
"a_800-850_Hamster1963": "@Hamster1963"
},
"Header": {
"p_1079-1199_Simpleandbeautifuldashbo": "简单美观的仪表板"
},
"Overview": {
"p_2277-2331_Overview": "👋 概览",
"p_2390-2457_wherethetimeis": "当前时间"
},
"NotFoundPage": {
"h1_490-590_404NotFound": "404 未找到",
"p_601-665_TARDISERROR": "TARDIS 错误!",
"Link_676-775_Doctor": "Doctor"
}
}

21
middleware.ts Normal file
View File

@ -0,0 +1,21 @@
// @auto-i18n-check. Please do not delete the line.
import createMiddleware from "next-intl/middleware";
import { locales } from "./i18n-metadata";
export default createMiddleware({
// A list of all locales that are supported
locales: locales,
// Used when no locale matches
defaultLocale: "en",
// 'always': This is the default, The home page will also be redirected to the default language, such as www.abc.com to www.abc.com/en
// 'as-needed': The default page is not redirected. For example, if you open www.abc.com, it is still www.abc.com
localePrefix: "as-needed",
});
export const config = {
// Match only internationalized pathnames
matcher: ["/", "/(en|zh|zh-t|ja)/:path*"],
};

View File

@ -1,5 +1,6 @@
import createNextIntlPlugin from "next-intl/plugin";
const withNextIntl = createNextIntlPlugin();
import withPWAInit from "@ducanh2912/next-pwa"; import withPWAInit from "@ducanh2912/next-pwa";
const withPWA = withPWAInit({ const withPWA = withPWAInit({
dest: "public", dest: "public",
cacheOnFrontEndNav: true, cacheOnFrontEndNav: true,
@ -16,5 +17,4 @@ const nextConfig = {
output: "standalone", output: "standalone",
reactStrictMode: true, reactStrictMode: true,
}; };
export default withPWA(withNextIntl(nextConfig));
export default withPWA(nextConfig);

View File

@ -3,10 +3,12 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 3020 --turbo", "dev": "next dev -p 3020",
"start": "node .next/standalone/server.js", "start": "node .next/standalone/server.js",
"lint": "next lint", "lint": "next lint",
"build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/" "build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/",
"build-dev": "next build",
"start-dev": "next start"
}, },
"dependencies": { "dependencies": {
"@ducanh2912/next-pwa": "^10.2.9", "@ducanh2912/next-pwa": "^10.2.9",
@ -20,6 +22,7 @@
"@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.2",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/eslint-plugin": "^7.18.0",
"caniuse-lite": "^1.0.30001664",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"country-flag-icons": "^1.5.13", "country-flag-icons": "^1.5.13",
@ -27,6 +30,7 @@
"lucide-react": "^0.414.0", "lucide-react": "^0.414.0",
"luxon": "^3.5.0", "luxon": "^3.5.0",
"next": "^14.2.13", "next": "^14.2.13",
"next-intl": "^3.20.0",
"next-runtime-env": "^3.2.2", "next-runtime-env": "^3.2.2",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"react": "^18.3.1", "react": "^18.3.1",
@ -43,16 +47,16 @@
"eslint-plugin-turbo": "^2.1.2", "eslint-plugin-turbo": "^2.1.2",
"eslint-plugin-unused-imports": "^4.1.4", "eslint-plugin-unused-imports": "^4.1.4",
"@next/bundle-analyzer": "^14.2.13", "@next/bundle-analyzer": "^14.2.13",
"@types/node": "^22.5.5", "@types/node": "^22.7.2",
"@types/react": "^18.3.8", "@types/react": "^18.3.9",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^9.11.0", "eslint": "^9.11.1",
"eslint-config-next": "^14.2.13", "eslint-config-next": "^14.2.13",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.6", "prettier-plugin-tailwindcss": "^0.6.8",
"tailwindcss": "^3.4.12", "tailwindcss": "^3.4.13",
"typescript": "^5.6.2" "typescript": "^5.6.2"
} }
} }

View File

@ -1 +0,0 @@
self.onmessage=async e=>{switch(e.data.type){case"__START_URL_CACHE__":{let t=e.data.url,a=await fetch(t);if(!a.redirected)return(await caches.open("start-url")).put(t,a);return Promise.resolve()}case"__FRONTEND_NAV_CACHE__":{let t=e.data.url,a=await caches.open("pages");if(await a.match(t,{ignoreSearch:!0}))return;let s=await fetch(t);if(!s.ok)return;if(a.put(t,s.clone()),e.data.shouldCacheAggressively&&s.headers.get("Content-Type")?.includes("text/html"))try{let e=await s.text(),t=[],a=await caches.open("static-style-assets"),r=await caches.open("next-static-js-assets"),c=await caches.open("static-js-assets");for(let[s,r]of e.matchAll(/<link.*?href=['"](.*?)['"].*?>/g))/rel=['"]stylesheet['"]/.test(s)&&t.push(a.match(r).then(e=>e?Promise.resolve():a.add(r)));for(let[,a]of e.matchAll(/<script.*?src=['"](.*?)['"].*?>/g)){let e=/\/_next\/static.+\.js$/i.test(a)?r:c;t.push(e.match(a).then(t=>t?Promise.resolve():e.add(a)))}return await Promise.all(t)}catch{}return Promise.resolve()}default:return Promise.resolve()}};