@ -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) => (
|
@ -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 font-medium md:text-base">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 font-medium md:text-base">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 font-medium md:text-base">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 font-medium md:text-base">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)}
|
@ -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>
|
@ -1,12 +1,14 @@
|
|||||||
"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">
|
||||||
@ -27,11 +29,14 @@ 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>
|
||||||
|
<section className="flex items-center gap-2">
|
||||||
|
<LanguageSwitcher />
|
||||||
<ModeToggle />
|
<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;
|
@ -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">
|
@ -1,18 +1,14 @@
|
|||||||
import ServerList from "@/components/ServerList";
|
import ServerList from "../../../components/ServerList";
|
||||||
import ServerOverview from "@/components/ServerOverview";
|
import ServerOverview from "../../../components/ServerOverview";
|
||||||
import getEnv from "@/lib/env-entry";
|
import getEnv from "../../../lib/env-entry";
|
||||||
import { GetNezhaData } from "@/lib/serverFetch";
|
import { GetNezhaData } from "../../../lib/serverFetch";
|
||||||
|
|
||||||
import { SWRConfig } from "swr";
|
import { SWRConfig } from "swr";
|
||||||
|
|
||||||
const disablePrefetch = getEnv("ServerDisablePrefetch") === "true";
|
const disablePrefetch = getEnv("ServerDisablePrefetch") === "true";
|
||||||
|
|
||||||
const fallback = disablePrefetch
|
const fallback = disablePrefetch
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
"/api/server": GetNezhaData(),
|
"/api/server": GetNezhaData(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<SWRConfig
|
<SWRConfig
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 263 B After Width: | Height: | Size: 263 B |
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 324 B |
@ -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
|
||||||
>
|
>
|
||||||
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||||
{children}
|
{children}
|
||||||
|
</NextIntlClientProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,7 +1,8 @@
|
|||||||
|
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 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">
|
||||||
@ -14,11 +15,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>
|
10
auto-i18n-config.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"defaultLang": "en",
|
||||||
|
"translatorServerName": "azure",
|
||||||
|
"needLangs": ["en", "zh", "zh-t", "ja"],
|
||||||
|
"brandWords": [],
|
||||||
|
"unMoveToLocaleDirFiles": [],
|
||||||
|
"enableStaticRendering": false,
|
||||||
|
"enableSubPageRedirectToLocale": false,
|
||||||
|
"disableDefaultLangRedirect": true
|
||||||
|
}
|
70
components/LanguageSwitcher.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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 />;
|
||||||
|
@ -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 />;
|
||||||
|
@ -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>
|
||||||
|
24
i18n-metadata.ts
Normal 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
@ -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,
|
||||||
|
};
|
||||||
|
});
|
@ -1,7 +1,7 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { NezhaAPI, ServerApi } from "@/app/types/nezha-api";
|
import { NezhaAPI, ServerApi } from "../app/[locale]/types/nezha-api";
|
||||||
import { MakeOptional } from "@/app/types/utils";
|
import { MakeOptional } from "../app/[locale]/types/utils";
|
||||||
import { error } from "console";
|
import { error } from "console";
|
||||||
import { unstable_noStore as noStore } from "next/cache";
|
import { unstable_noStore as noStore } from "next/cache";
|
||||||
import getEnv from "./env-entry";
|
import getEnv from "./env-entry";
|
||||||
|
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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*"],
|
||||||
|
};
|
@ -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);
|
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
"dev": "next dev -p 3020",
|
"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,7 +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.30001663",
|
"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",
|
||||||
@ -28,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",
|
||||||
@ -44,7 +47,7 @@
|
|||||||
"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.7.0",
|
"@types/node": "^22.7.2",
|
||||||
"@types/react": "^18.3.9",
|
"@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",
|
||||||
|
@ -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()}};
|
|