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
a62555dfba
@ -181,13 +181,7 @@ export const NetworkChart = React.memo(function NetworkChart({
|
||||
<Card>
|
||||
<CardHeader className="flex flex-col items-stretch space-y-0 p-0 sm:flex-row">
|
||||
<div className="flex flex-none flex-col justify-center gap-1 border-b px-6 py-4">
|
||||
<CardTitle
|
||||
onClick={() => {
|
||||
router.push(`/${locale}/`);
|
||||
}}
|
||||
className="flex flex-none cursor-pointer items-center gap-0.5 text-xl"
|
||||
>
|
||||
<BackIcon />
|
||||
<CardTitle className="flex flex-none items-center gap-0.5 text-md">
|
||||
{serverName}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs">
|
||||
|
@ -12,13 +12,7 @@ export default function NetworkChartLoading() {
|
||||
<Card>
|
||||
<CardHeader className="flex flex-col items-stretch space-y-0 border-b p-0 sm:flex-row">
|
||||
<div className="flex flex-1 flex-col justify-center gap-1 px-6 py-5">
|
||||
<CardTitle
|
||||
onClick={() => {
|
||||
router.push(`/${locale}/`);
|
||||
}}
|
||||
className="flex items-center cursor-pointer gap-0.5 text-xl"
|
||||
>
|
||||
<BackIcon />
|
||||
<CardTitle className="flex items-center gap-0.5 text-xl">
|
||||
<div className="aspect-auto h-[20px] w-24 bg-muted"></div>
|
||||
</CardTitle>
|
||||
<div className="mt-[2px] aspect-auto h-[14px] w-32 bg-muted"></div>
|
||||
|
@ -10,6 +10,7 @@ import getEnv from "@/lib/env-entry";
|
||||
import { cn, formatBytes, nezhaFetcher } from "@/lib/utils";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
export default function ServerDetailClient({
|
||||
@ -20,6 +21,32 @@ export default function ServerDetailClient({
|
||||
const t = useTranslations("ServerDetailClient");
|
||||
const router = useRouter();
|
||||
const locale = useLocale();
|
||||
|
||||
const [hasHistory, setHasHistory] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const previousPath = sessionStorage.getItem("lastPath");
|
||||
const currentPath = window.location.pathname;
|
||||
|
||||
if (previousPath && previousPath !== currentPath) {
|
||||
setHasHistory(true);
|
||||
} else {
|
||||
sessionStorage.setItem("lastPath", currentPath);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const linkClick = () => {
|
||||
if (hasHistory) {
|
||||
router.back();
|
||||
} else {
|
||||
router.push(`/${locale}/`);
|
||||
}
|
||||
};
|
||||
|
||||
const { data, error } = useSWR<NezhaAPISafe>(
|
||||
`/api/detail?server_id=${server_id}`,
|
||||
nezhaFetcher,
|
||||
@ -46,9 +73,7 @@ export default function ServerDetailClient({
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
onClick={() => {
|
||||
router.push(`/${locale}/`);
|
||||
}}
|
||||
onClick={linkClick}
|
||||
className="flex flex-none cursor-pointer font-semibold leading-none items-center break-all tracking-tight gap-0.5 text-xl"
|
||||
>
|
||||
<BackIcon />
|
||||
|
@ -6,17 +6,53 @@ import Switch from "@/components/Switch";
|
||||
import getEnv from "@/lib/env-entry";
|
||||
import { nezhaFetcher } from "@/lib/utils";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
export default function ServerListClient() {
|
||||
const t = useTranslations("ServerListClient");
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [tag, setTag] = useState<string>(t("defaultTag"));
|
||||
const defaultTag = t("defaultTag");
|
||||
const [tag, setTag] = useState<string>(
|
||||
sessionStorage.getItem("selectedTag") || defaultTag,
|
||||
);
|
||||
|
||||
const handleTagChange = (newTag: string) => {
|
||||
setTag(newTag);
|
||||
sessionStorage.setItem("selectedTag", newTag);
|
||||
sessionStorage.setItem(
|
||||
"scrollPosition",
|
||||
String(containerRef.current?.scrollTop || 0),
|
||||
);
|
||||
};
|
||||
|
||||
const restoreScrollPosition = () => {
|
||||
const savedPosition = sessionStorage.getItem("scrollPosition");
|
||||
if (savedPosition && containerRef.current) {
|
||||
containerRef.current.scrollTop = Number(savedPosition);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
restoreScrollPosition();
|
||||
}, [tag]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = () => {
|
||||
restoreScrollPosition();
|
||||
};
|
||||
|
||||
window.addEventListener("popstate", handleRouteChange);
|
||||
return () => {
|
||||
window.removeEventListener("popstate", handleRouteChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { data, error } = useSWR<ServerApi>("/api/server", nezhaFetcher, {
|
||||
refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000,
|
||||
});
|
||||
|
||||
if (error)
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
@ -24,32 +60,38 @@ export default function ServerListClient() {
|
||||
<p className="text-sm font-medium opacity-40">{t("error_message")}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!data?.result) return null;
|
||||
|
||||
const { result } = data;
|
||||
|
||||
const sortedServers = result.sort((a, b) => {
|
||||
const displayIndexDiff = (b.display_index || 0) - (a.display_index || 0);
|
||||
if (displayIndexDiff !== 0) return displayIndexDiff;
|
||||
return a.id - b.id;
|
||||
});
|
||||
|
||||
const allTag = sortedServers.map((server) => server.tag).filter((tag) => tag);
|
||||
const allTag = sortedServers.map((server) => server.tag).filter(Boolean);
|
||||
const uniqueTags = [...new Set(allTag)];
|
||||
|
||||
uniqueTags.unshift(t("defaultTag"));
|
||||
uniqueTags.unshift(defaultTag);
|
||||
|
||||
const filteredServers =
|
||||
tag === t("defaultTag")
|
||||
tag === defaultTag
|
||||
? sortedServers
|
||||
: sortedServers.filter((server) => server.tag === tag);
|
||||
|
||||
return (
|
||||
<>
|
||||
{getEnv("NEXT_PUBLIC_ShowTag") === "true" && uniqueTags.length > 1 && (
|
||||
<Switch allTag={uniqueTags} nowTag={tag} setTag={setTag} />
|
||||
<Switch
|
||||
allTag={uniqueTags}
|
||||
nowTag={tag}
|
||||
onTagChange={handleTagChange}
|
||||
/>
|
||||
)}
|
||||
<section className="grid grid-cols-1 gap-2 md:grid-cols-2">
|
||||
<section
|
||||
ref={containerRef}
|
||||
className="grid grid-cols-1 gap-2 md:grid-cols-2"
|
||||
>
|
||||
{filteredServers.map((serverInfo) => (
|
||||
<ServerCard key={serverInfo.id} serverInfo={serverInfo} />
|
||||
))}
|
||||
|
@ -2,24 +2,55 @@
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import React from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
export default function Switch({
|
||||
allTag,
|
||||
nowTag,
|
||||
setTag,
|
||||
onTagChange,
|
||||
}: {
|
||||
allTag: string[];
|
||||
nowTag: string;
|
||||
setTag: (tag: string) => void;
|
||||
onTagChange: (tag: string) => void;
|
||||
}) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const savedTag = sessionStorage.getItem("selectedTag");
|
||||
if (savedTag && allTag.includes(savedTag)) {
|
||||
onTagChange(savedTag);
|
||||
}
|
||||
}, [allTag]);
|
||||
|
||||
useEffect(() => {
|
||||
const container = scrollRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const isOverflowing = container.scrollWidth > container.clientWidth;
|
||||
if (!isOverflowing) return;
|
||||
|
||||
const onWheel = (e: WheelEvent) => {
|
||||
e.preventDefault();
|
||||
container.scrollLeft += e.deltaY;
|
||||
};
|
||||
|
||||
container.addEventListener("wheel", onWheel, { passive: false });
|
||||
|
||||
return () => {
|
||||
container.removeEventListener("wheel", onWheel);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="scrollbar-hidden z-50 flex flex-col items-start overflow-x-scroll rounded-[50px]">
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="scrollbar-hidden z-50 flex flex-col items-start overflow-x-scroll rounded-[50px]"
|
||||
>
|
||||
<div className="flex items-center gap-1 rounded-[50px] bg-stone-100 p-[3px] dark:bg-stone-800">
|
||||
{allTag.map((tag) => (
|
||||
<div
|
||||
key={tag}
|
||||
onClick={() => setTag(tag)}
|
||||
onClick={() => onTagChange(tag)}
|
||||
className={cn(
|
||||
"relative cursor-pointer rounded-3xl px-2.5 py-[8px] text-[13px] font-[600] transition-all duration-500",
|
||||
nowTag === tag
|
||||
|
@ -3,7 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3020 --turbo",
|
||||
"dev": "next dev -p 3020",
|
||||
"start": "node .next/standalone/server.js",
|
||||
"lint": "next lint",
|
||||
"build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/",
|
||||
|
Loading…
Reference in New Issue
Block a user