mirror of
https://github.com/hamster1963/nezha-dash.git
synced 2025-04-24 21:10:45 +08:00
Merge branch 'main' into cloudflare
This commit is contained in:
commit
16b08888bc
@ -12,7 +12,7 @@ NEXT_PUBLIC_FixedTopServerName=false
|
|||||||
NEXT_PUBLIC_CustomLogo=https://nezha-cf.buycoffee.top/apple-touch-icon.png
|
NEXT_PUBLIC_CustomLogo=https://nezha-cf.buycoffee.top/apple-touch-icon.png
|
||||||
NEXT_PUBLIC_CustomTitle=NezhaDash
|
NEXT_PUBLIC_CustomTitle=NezhaDash
|
||||||
NEXT_PUBLIC_CustomDescription=NezhaDash is a dashboard for Nezha.
|
NEXT_PUBLIC_CustomDescription=NezhaDash is a dashboard for Nezha.
|
||||||
NEXT_PUBLIC_Links=[{"link":"https://baidu.com","name":"Baidu"},{"link":"https://google.com","name":"Google"}]
|
NEXT_PUBLIC_Links="[{"link":"https://github.com/hamster1963/nezha-dash","name":"GitHub"},{"link":"https://buycoffee.top/coffee","name":"Buycoffee☕️"}]"
|
||||||
NEXT_PUBLIC_DisableIndex=false
|
NEXT_PUBLIC_DisableIndex=false
|
||||||
NEXT_PUBLIC_BASE_PATH=/
|
NEXT_PUBLIC_BASE_PATH=/
|
||||||
NEXT_PUBLIC_ShowTagCount=false
|
NEXT_PUBLIC_ShowTagCount=false
|
||||||
|
@ -4,12 +4,14 @@ import { ServerApi } from "@/app/types/nezha-api";
|
|||||||
import ServerCard from "@/components/ServerCard";
|
import ServerCard from "@/components/ServerCard";
|
||||||
import Switch from "@/components/Switch";
|
import Switch from "@/components/Switch";
|
||||||
import getEnv from "@/lib/env-entry";
|
import getEnv from "@/lib/env-entry";
|
||||||
|
import { useStatus } from "@/lib/status-context";
|
||||||
import { nezhaFetcher } from "@/lib/utils";
|
import { nezhaFetcher } from "@/lib/utils";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
export default function ServerListClient() {
|
export default function ServerListClient() {
|
||||||
|
const { status } = useStatus();
|
||||||
const t = useTranslations("ServerListClient");
|
const t = useTranslations("ServerListClient");
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const defaultTag = "defaultTag";
|
const defaultTag = "defaultTag";
|
||||||
@ -73,17 +75,26 @@ export default function ServerListClient() {
|
|||||||
return a.id - b.id;
|
return a.id - b.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const allTag = sortedServers.map((server) => server.tag).filter(Boolean);
|
const filteredServersByStatus =
|
||||||
|
status === "all"
|
||||||
|
? sortedServers
|
||||||
|
: sortedServers.filter((server) =>
|
||||||
|
[status].includes(server.online_status ? "online" : "offline"),
|
||||||
|
);
|
||||||
|
|
||||||
|
const allTag = filteredServersByStatus
|
||||||
|
.map((server) => server.tag)
|
||||||
|
.filter(Boolean);
|
||||||
const uniqueTags = [...new Set(allTag)];
|
const uniqueTags = [...new Set(allTag)];
|
||||||
uniqueTags.unshift(defaultTag);
|
uniqueTags.unshift(defaultTag);
|
||||||
|
|
||||||
const filteredServers =
|
const filteredServers =
|
||||||
tag === defaultTag
|
tag === defaultTag
|
||||||
? sortedServers
|
? filteredServersByStatus
|
||||||
: sortedServers.filter((server) => server.tag === tag);
|
: filteredServersByStatus.filter((server) => server.tag === tag);
|
||||||
|
|
||||||
const tagCountMap: Record<string, number> = {};
|
const tagCountMap: Record<string, number> = {};
|
||||||
sortedServers.forEach((server) => {
|
filteredServersByStatus.forEach((server) => {
|
||||||
if (server.tag) {
|
if (server.tag) {
|
||||||
tagCountMap[server.tag] = (tagCountMap[server.tag] || 0) + 1;
|
tagCountMap[server.tag] = (tagCountMap[server.tag] || 0) + 1;
|
||||||
}
|
}
|
||||||
@ -91,7 +102,7 @@ export default function ServerListClient() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{getEnv("NEXT_PUBLIC_ShowTag") === "true" && uniqueTags.length > 1 && (
|
{getEnv("NEXT_PUBLIC_ShowTag") === "true" && (
|
||||||
<Switch
|
<Switch
|
||||||
allTag={uniqueTags}
|
allTag={uniqueTags}
|
||||||
nowTag={tag}
|
nowTag={tag}
|
||||||
|
@ -4,13 +4,15 @@ import { ServerApi } from "@/app/types/nezha-api";
|
|||||||
import { Loader } from "@/components/loading/Loader";
|
import { Loader } from "@/components/loading/Loader";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import getEnv from "@/lib/env-entry";
|
import getEnv from "@/lib/env-entry";
|
||||||
import { formatBytes, nezhaFetcher } from "@/lib/utils";
|
import { useStatus } from "@/lib/status-context";
|
||||||
|
import { cn, formatBytes, nezhaFetcher } from "@/lib/utils";
|
||||||
import blogMan from "@/public/blog-man.webp";
|
import blogMan from "@/public/blog-man.webp";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
export default function ServerOverviewClient() {
|
export default function ServerOverviewClient() {
|
||||||
|
const { status, setStatus } = useStatus();
|
||||||
const t = useTranslations("ServerOverviewClient");
|
const t = useTranslations("ServerOverviewClient");
|
||||||
const { data, error, isLoading } = useSWR<ServerApi>(
|
const { data, error, isLoading } = useSWR<ServerApi>(
|
||||||
"/api/server",
|
"/api/server",
|
||||||
@ -32,7 +34,10 @@ export default function ServerOverviewClient() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
<section className="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||||
<Card>
|
<Card
|
||||||
|
onClick={() => setStatus("all")}
|
||||||
|
className="cursor-pointer hover:border-blue-500 transition-all"
|
||||||
|
>
|
||||||
<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">
|
<p className="text-sm font-medium md:text-base">
|
||||||
@ -55,7 +60,15 @@ export default function ServerOverviewClient() {
|
|||||||
</section>
|
</section>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card
|
||||||
|
onClick={() => setStatus("online")}
|
||||||
|
className={cn(
|
||||||
|
"cursor-pointer hover:ring-green-500 ring-1 ring-transparent transition-all",
|
||||||
|
{
|
||||||
|
"ring-green-500 ring-2 border-transparent": status === "online",
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
<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">
|
<p className="text-sm font-medium md:text-base">
|
||||||
@ -79,7 +92,15 @@ export default function ServerOverviewClient() {
|
|||||||
</section>
|
</section>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card
|
||||||
|
onClick={() => setStatus("offline")}
|
||||||
|
className={cn(
|
||||||
|
"cursor-pointer hover:ring-red-500 ring-1 ring-transparent transition-all",
|
||||||
|
{
|
||||||
|
"ring-red-500 ring-2 border-transparent": status === "offline",
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
<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">
|
<p className="text-sm font-medium md:text-base">
|
||||||
|
@ -71,8 +71,6 @@ function Links() {
|
|||||||
|
|
||||||
const links: links[] | null = linksEnv ? JSON.parse(linksEnv) : null;
|
const links: links[] | null = linksEnv ? JSON.parse(linksEnv) : null;
|
||||||
|
|
||||||
console.log(links);
|
|
||||||
|
|
||||||
if (!links) return null;
|
if (!links) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -96,7 +94,7 @@ function Links() {
|
|||||||
|
|
||||||
// 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: () => void, delay: number | null) => {
|
const useInterval = (callback: () => void, delay: number | null) => {
|
||||||
const savedCallback = useRef<() => void>(() => {});
|
const savedCallback = useRef<() => void>(() => { });
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
savedCallback.current = callback;
|
savedCallback.current = callback;
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// @auto-i18n-check. Please do not delete the line.
|
// @auto-i18n-check. Please do not delete the line.
|
||||||
import { MotionProvider } from "@/components/motion/motion-provider";
|
import { MotionProvider } from "@/components/motion/motion-provider";
|
||||||
import getEnv from "@/lib/env-entry";
|
import getEnv from "@/lib/env-entry";
|
||||||
|
import { StatusProvider } from "@/lib/status-context";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
@ -78,7 +79,7 @@ export default async function LocaleLayout({
|
|||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<NextIntlClientProvider messages={messages}>
|
<NextIntlClientProvider messages={messages}>
|
||||||
{children}
|
<StatusProvider>{children}</StatusProvider>
|
||||||
</NextIntlClientProvider>
|
</NextIntlClientProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</MotionProvider>
|
</MotionProvider>
|
||||||
|
@ -12,5 +12,6 @@ NEXT_PUBLIC_FixedTopServerName=false
|
|||||||
NEXT_PUBLIC_CustomLogo=https://nezha-cf.buycoffee.top/apple-touch-icon.png
|
NEXT_PUBLIC_CustomLogo=https://nezha-cf.buycoffee.top/apple-touch-icon.png
|
||||||
NEXT_PUBLIC_CustomTitle=NezhaDash
|
NEXT_PUBLIC_CustomTitle=NezhaDash
|
||||||
NEXT_PUBLIC_CustomDescription=NezhaDash is a dashboard for Nezha.
|
NEXT_PUBLIC_CustomDescription=NezhaDash is a dashboard for Nezha.
|
||||||
|
NEXT_PUBLIC_Links="[{"link":"https://github.com/hamster1963/nezha-dash","name":"GitHub"},{"link":"https://buycoffee.top/coffee","name":"Buycoffee☕️"}]"
|
||||||
NEXT_PUBLIC_DisableIndex=false
|
NEXT_PUBLIC_DisableIndex=false
|
||||||
NEXT_PUBLIC_ShowTagCount=false
|
NEXT_PUBLIC_ShowTagCount=false
|
30
lib/status-context.tsx
Normal file
30
lib/status-context.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { ReactNode, createContext, useContext, useState } from "react";
|
||||||
|
|
||||||
|
type Status = "all" | "online" | "offline";
|
||||||
|
|
||||||
|
interface StatusContextType {
|
||||||
|
status: Status;
|
||||||
|
setStatus: (status: Status) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatusContext = createContext<StatusContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export function StatusProvider({ children }: { children: ReactNode }) {
|
||||||
|
const [status, setStatus] = useState<Status>("all");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StatusContext.Provider value={{ status, setStatus }}>
|
||||||
|
{children}
|
||||||
|
</StatusContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useStatus() {
|
||||||
|
const context = useContext(StatusContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error("useStatus must be used within a StatusProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nezha-dash",
|
"name": "nezha-dash",
|
||||||
"version": "1.3.1",
|
"version": "1.3.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3020",
|
"dev": "next dev -p 3020",
|
||||||
@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ducanh2912/next-pwa": "^10.2.9",
|
"@ducanh2912/next-pwa": "^10.2.9",
|
||||||
"@heroicons/react": "^2.1.5",
|
"@heroicons/react": "^2.2.0",
|
||||||
"@radix-ui/react-dialog": "^1.1.2",
|
"@radix-ui/react-dialog": "^1.1.2",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.1",
|
"@radix-ui/react-navigation-menu": "^1.2.1",
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.4",
|
"@radix-ui/react-tooltip": "^1.1.4",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.14.0",
|
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
||||||
"caniuse-lite": "^1.0.30001680",
|
"caniuse-lite": "^1.0.30001680",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@ -48,7 +48,7 @@
|
|||||||
"swr": "^2.2.6-beta.4",
|
"swr": "^2.2.6-beta.4",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript-eslint": "^8.14.0"
|
"typescript-eslint": "^8.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint-plugin-turbo": "^2.3.0",
|
"eslint-plugin-turbo": "^2.3.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user