commit 3085a41b117e21e27ad397c5630f017b41011c9c
Author: hamster1963 <1410514192@qq.com>
Date: Sat Jul 27 02:17:07 2024 +0800
first commit
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..735293d
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,2 @@
+NezhaBaseUrl=http://0.0.0.0:8008
+NezhaAuth=5hAY3QX6Nl9B3UOQgB26KdsdS1dsdUdM
\ No newline at end of file
diff --git a/.github/shotOne.png b/.github/shotOne.png
new file mode 100644
index 0000000..17a961a
Binary files /dev/null and b/.github/shotOne.png differ
diff --git a/.github/shotTwo.png b/.github/shotTwo.png
new file mode 100644
index 0000000..e8f0d94
Binary files /dev/null and b/.github/shotTwo.png differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c8ea49c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,48 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+# pwa
+/public/sw.js
+/public/sw.js.map
+/public/swe-worker-development.js
+/public/workbox*.js
+/public/workbox*.js.map
+
+/.idea/
+
+.env
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fda6d95
--- /dev/null
+++ b/README.md
@@ -0,0 +1,10 @@
+
HomeDash
+
+HomeDash 是一个基于 Next.js 和 Shadcn 的仪表盘
+
+Demo地址: https://dash.buycoffee.top
+
+
+
+
+
diff --git a/app/(main)/ClientComponents/LiveTag.tsx b/app/(main)/ClientComponents/LiveTag.tsx
new file mode 100644
index 0000000..46cba27
--- /dev/null
+++ b/app/(main)/ClientComponents/LiveTag.tsx
@@ -0,0 +1,43 @@
+"use client";
+import { useEffect, useState } from "react";
+import { toast } from "sonner";
+
+import { Badge } from "@/components/ui/badge";
+import { verifySSEConnection } from "@/lib/sseFetch";
+
+export default function LiveTag() {
+ const [connected, setConnected] = useState(false);
+
+ useEffect(() => {
+ // Store the promise in a variable
+ const ssePromise = verifySSEConnection(
+ "https://home.buycoffee.tech/v2/VerifySSEConnect",
+ );
+ setTimeout(() => {
+ toast.promise(ssePromise, {
+ loading: "Connecting to SSE...",
+ success: "HomeDash SSE Connected",
+ error: "Error connecting to SSE",
+ });
+ });
+ // Handle promise resolution separately
+ ssePromise
+ .then(() => {
+ setConnected(true);
+ })
+ .catch(() => {
+ setConnected(false);
+ });
+ }, []);
+
+ return connected ? (
+
+ Synced
+
+
+ ) : (
+
+ Static
+
+ );
+}
diff --git a/app/(main)/ClientComponents/ServerListClient.tsx b/app/(main)/ClientComponents/ServerListClient.tsx
new file mode 100644
index 0000000..a8fe942
--- /dev/null
+++ b/app/(main)/ClientComponents/ServerListClient.tsx
@@ -0,0 +1,34 @@
+"use client";
+
+import ServerCard from "@/components/ServerCard";
+import { nezhaFetcher } from "@/lib/utils";
+import useSWR from "swr";
+
+export default function ServerListClient() {
+ const { data } = useSWR('/api/server', nezhaFetcher, {
+ refreshInterval: 2000,
+ });
+ if (!data) return null;
+ const sortedResult = data.result.sort((a: any, b: any) => a.id - b.id);
+
+ return (
+
+ {sortedResult.map(
+ (server: any) => (
+
+ ),
+ )}
+
+ );
+}
diff --git a/app/(main)/ClientComponents/ServerOverviewClient.tsx b/app/(main)/ClientComponents/ServerOverviewClient.tsx
new file mode 100644
index 0000000..cedebd4
--- /dev/null
+++ b/app/(main)/ClientComponents/ServerOverviewClient.tsx
@@ -0,0 +1,75 @@
+"use client";
+
+import { Card, CardContent } from "@/components/ui/card";
+import blogMan from "@/public/blog-man.webp";
+import Image from "next/image";
+import useSWR from "swr";
+import { formatBytes, nezhaFetcher } from "@/lib/utils";
+import { Loader } from "@/components/loading/Loader";
+
+export default function ServerOverviewClient() {
+ const { data } = useSWR('/api/server', nezhaFetcher);
+
+ return (
+
+
+
+
+ Total servers
+
+
+
+
+ {data ?
{data?.result.length}
:
}
+
+
+
+
+
+
+
+ Online servers
+
+
+
+
+
+ {data ?
{data?.live_servers}
:
}
+
+
+
+
+
+
+
+ Offline servers
+
+
+
+
+
+ {data ?
{data?.offline_servers}
:
}
+
+
+
+
+
+
+
+
+
+ Total bandwidth
+ {data ? {formatBytes(data?.total_bandwidth)}
:
}
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/app/(main)/header.tsx b/app/(main)/header.tsx
new file mode 100644
index 0000000..f062240
--- /dev/null
+++ b/app/(main)/header.tsx
@@ -0,0 +1,62 @@
+"use client";
+
+import React, { useEffect, useState } from "react";
+import Image from "next/image";
+import { Separator } from "@/components/ui/separator";
+import { DateTime } from "luxon";
+
+function Header() {
+ return (
+
+
+
+
+
+
+ HomeDash
+
+
+ Simple and beautiful dashboard
+
+
+ {/* */}
+
+
+
+ );
+}
+
+function Overview() {
+ const [mouted, setMounted] = useState(false);
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+ const time = DateTime.TIME_SIMPLE;
+ time.hour12 = true;
+
+ return (
+
+ 👋 Overview
+
+
where the time is
+ {mouted && (
+
+ {DateTime.now().setLocale("en-US").toLocaleString(time)}
+
+ )}
+
+
+ );
+}
+
+export default Header;
diff --git a/app/(main)/layout.tsx b/app/(main)/layout.tsx
new file mode 100644
index 0000000..19b2d6a
--- /dev/null
+++ b/app/(main)/layout.tsx
@@ -0,0 +1,21 @@
+import React from "react";
+
+import Header from "@/app/(main)/header";
+import BlurLayers from "@/components/BlurLayer";
+
+type DashboardProps = {
+ children: React.ReactNode;
+};
+
+export default function MainLayout({ children }: DashboardProps) {
+ return (
+
+
+
+
+ {/* */}
+ {children}
+
+
+ );
+}
diff --git a/app/(main)/nav.tsx b/app/(main)/nav.tsx
new file mode 100644
index 0000000..3f8b907
--- /dev/null
+++ b/app/(main)/nav.tsx
@@ -0,0 +1,57 @@
+"use client";
+
+import { clsx } from "clsx";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import React from "react";
+
+import { cn } from "@/lib/utils";
+
+export const siteUrlList = [
+ {
+ name: "Home",
+ header: "👋 Overview",
+ url: "/",
+ },
+ {
+ name: "Service",
+ header: "🎛️ Service",
+ url: "/service",
+ },
+];
+export default function Nav() {
+ const nowPath = usePathname();
+ return (
+
+
+ {siteUrlList.map((site, index) => (
+
+ {index !== 0 && (
+
+ /
+
+ )}
+
+ {site.name}
+
+
+ ))}
+
+
+ );
+}
diff --git a/app/(main)/page.tsx b/app/(main)/page.tsx
new file mode 100644
index 0000000..a03186d
--- /dev/null
+++ b/app/(main)/page.tsx
@@ -0,0 +1,15 @@
+import ServerList from "@/components/ServerList";
+import ServerOverview from "@/components/ServerOverview";
+
+
+
+export default function Home() {
+
+ return (
+
+ );
+}
diff --git a/app/android-chrome-192x192.png b/app/android-chrome-192x192.png
new file mode 100644
index 0000000..6b354f0
Binary files /dev/null and b/app/android-chrome-192x192.png differ
diff --git a/app/android-chrome-512x512.png b/app/android-chrome-512x512.png
new file mode 100644
index 0000000..516d91b
Binary files /dev/null and b/app/android-chrome-512x512.png differ
diff --git a/app/api/server/route.ts b/app/api/server/route.ts
new file mode 100644
index 0000000..ac94199
--- /dev/null
+++ b/app/api/server/route.ts
@@ -0,0 +1,33 @@
+
+import { NextResponse } from "next/server";
+
+export async function GET(_: Request) {
+
+ try {
+ const response = await fetch(process.env.NezhaBaseUrl+ '/api/v1/server/details',{
+ headers: {
+ 'Authorization': process.env.NezhaAuth as string
+ },
+ next:{
+ revalidate:1
+ }
+ });
+ const data = await response.json();
+ data.live_servers = 0;
+ data.offline_servers = 0;
+ data.total_bandwidth = 0;
+
+ data.result.forEach((element: { status: { Uptime: number; NetOutTransfer: any; }; }) => {
+ if (element.status.Uptime !== 0) {
+ data.live_servers += 1;
+ } else {
+ data.offline_servers += 1;
+ }
+ data.total_bandwidth += element.status.NetOutTransfer;
+ });
+ return NextResponse.json(data, { status: 200 })
+ } catch (error) {
+ return NextResponse.json({ error: error }, { status: 200 })
+ }
+
+}
\ No newline at end of file
diff --git a/app/apple-touch-icon.png b/app/apple-touch-icon.png
new file mode 100644
index 0000000..5e07a89
Binary files /dev/null and b/app/apple-touch-icon.png differ
diff --git a/app/favicon-16x16.png b/app/favicon-16x16.png
new file mode 100644
index 0000000..7d0e77e
Binary files /dev/null and b/app/favicon-16x16.png differ
diff --git a/app/favicon-32x32.png b/app/favicon-32x32.png
new file mode 100644
index 0000000..9a7598e
Binary files /dev/null and b/app/favicon-32x32.png differ
diff --git a/app/favicon.ico b/app/favicon.ico
new file mode 100644
index 0000000..bb78ebd
Binary files /dev/null and b/app/favicon.ico differ
diff --git a/app/layout.tsx b/app/layout.tsx
new file mode 100644
index 0000000..da61a5f
--- /dev/null
+++ b/app/layout.tsx
@@ -0,0 +1,52 @@
+import "@/styles/globals.css";
+
+import type { Metadata } from "next";
+import { Inter as FontSans } from "next/font/google";
+import { ThemeProvider } from "next-themes";
+import React from "react";
+
+import NextThemeToaster from "@/components/client/NextToast";
+import { cn } from "@/lib/utils";
+
+const fontSans = FontSans({
+ subsets: ["latin"],
+ variable: "--font-sans",
+});
+
+export const metadata: Metadata = {
+ manifest: "/manifest.json",
+ title: "HomeDash",
+ description: "A dashboard for nezha",
+ appleWebApp: {
+ capable: true,
+ title: "HomeDash",
+ statusBarStyle: "black-translucent",
+ },
+};
+
+interface RootLayoutProps {
+ children: React.ReactNode;
+}
+
+export default function RootLayout({ children }: RootLayoutProps) {
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/app/not-found.tsx b/app/not-found.tsx
new file mode 100644
index 0000000..3c78834
--- /dev/null
+++ b/app/not-found.tsx
@@ -0,0 +1,27 @@
+import Image from "next/image";
+import Link from "next/link";
+
+export default function NotFoundPage() {
+ return (
+
+
+
+
+
+ 404 Not Found
+
+
TARDIS ERROR!
+
+ Doctor?
+
+
+
+
+ );
+}
diff --git a/bun.lockb b/bun.lockb
new file mode 100755
index 0000000..23316e6
Binary files /dev/null and b/bun.lockb differ
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..ef381e8
--- /dev/null
+++ b/components.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "app/globals.css",
+ "baseColor": "stone",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils"
+ }
+}
diff --git a/components/BlurLayer.tsx b/components/BlurLayer.tsx
new file mode 100644
index 0000000..745b752
--- /dev/null
+++ b/components/BlurLayer.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+const BlurLayers = () => {
+ const computeLayerStyle = (index: number) => {
+ const blurAmount = index * 3.7037;
+ const maskStart = index * 10;
+ let maskEnd = maskStart + 20;
+ if (maskEnd > 100) {
+ maskEnd = 100;
+ }
+ return {
+ backdropFilter: `blur(${blurAmount}px)`,
+ WebkitBackdropFilter: `blur(${blurAmount}px)`,
+ zIndex: index + 1,
+ maskImage: `linear-gradient(rgba(0, 0, 0, 0) ${maskStart}%, rgb(0, 0, 0) ${maskEnd}%)`,
+ };
+ };
+
+ // 根据层数动态生成层
+ const layers = Array.from({ length: 5 }).map((_, index) => (
+
+ ));
+
+ return (
+
+ );
+};
+
+export default BlurLayers;
diff --git a/components/Icon.tsx b/components/Icon.tsx
new file mode 100644
index 0000000..aa97669
--- /dev/null
+++ b/components/Icon.tsx
@@ -0,0 +1,7 @@
+export function GitHubIcon(props: React.ComponentPropsWithoutRef<"svg">) {
+ return (
+
+ );
+}
diff --git a/components/ServerCard.tsx b/components/ServerCard.tsx
new file mode 100644
index 0000000..c9757b6
--- /dev/null
+++ b/components/ServerCard.tsx
@@ -0,0 +1,94 @@
+import React from "react";
+
+import ServerUsageBar from "@/components/ServerUsageBar";
+import { Card } from "@/components/ui/card";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+
+type ServerCardProps = {
+ id: number;
+ status: string;
+ name: string;
+ uptime: number;
+ cpu: number;
+ mem: number;
+ stg: number;
+ up: number;
+ down: number;
+};
+
+export default function ServerCard({
+ status,
+ name,
+ uptime,
+ cpu,
+ mem,
+ stg,
+ up,
+ down,
+}: ServerCardProps) {
+ return status === "online" ? (
+
+
+
+
+
+
+ Online: {uptime.toFixed(0)} Days
+
+
+
+
+
CPU
+
{cpu.toFixed(2)}%
+
+
+
+
Mem
+
{mem.toFixed(2)}%
+
+
+
+
STG
+
{stg.toFixed(2)}%
+
+
+
+
Upload
+
{up.toFixed(2)}Mb/s
+
+
+
Download
+
{down.toFixed(2)}Mb/s
+
+
+
+ ) : (
+
+
+
+
+
+
+ Offline
+
+
+
+ );
+}
diff --git a/components/ServerList.tsx b/components/ServerList.tsx
new file mode 100644
index 0000000..db72679
--- /dev/null
+++ b/components/ServerList.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+
+import ServerListClient from "@/app/(main)/ClientComponents/ServerListClient";
+
+
+
+export default async function ServerList() {
+ return (
+
+ );
+}
diff --git a/components/ServerOverview.tsx b/components/ServerOverview.tsx
new file mode 100644
index 0000000..d5523fa
--- /dev/null
+++ b/components/ServerOverview.tsx
@@ -0,0 +1,12 @@
+import ServerOverviewClient from "@/app/(main)/ClientComponents/ServerOverviewClient";
+
+
+
+
+export default async function ServerOverview() {
+ return (
+
+ )
+
+
+}
\ No newline at end of file
diff --git a/components/ServerUsageBar.tsx b/components/ServerUsageBar.tsx
new file mode 100644
index 0000000..8c5b72b
--- /dev/null
+++ b/components/ServerUsageBar.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+
+import { Progress } from "@/components/ui/progress";
+
+type ServerUsageBarProps = {
+ value: number;
+};
+
+export default function ServerUsageBar({ value }: ServerUsageBarProps) {
+ return (
+