Merge branch 'cloudflare' into cloudflare-dev

This commit is contained in:
hamster1963 2024-10-21 03:02:42 +08:00
commit f248869b35
17 changed files with 125 additions and 131 deletions

View File

@ -30,13 +30,6 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to AliYun Container Registry
uses: docker/login-action@v3
with:
registry: registry.cn-guangzhou.aliyuncs.com
username: ${{ secrets.ALI_USERNAME }}
password: ${{ secrets.ALI_TOKEN }}
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5

View File

@ -25,8 +25,8 @@ jobs:
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4 uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
with: with:
upstream_sync_repo: hamster1963/nezha-dash upstream_sync_repo: hamster1963/nezha-dash
upstream_sync_branch: main upstream_sync_branch: cloudflare
target_sync_branch: main target_sync_branch: cloudflare
target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set
# Set test_mode true to run tests instead of the true action!! # Set test_mode true to run tests instead of the true action!!

View File

@ -8,34 +8,26 @@
| 一键部署到 Vercel-推荐 | Docker部署 | Cloudflare部署 | 如何更新? | | 一键部署到 Vercel-推荐 | Docker部署 | Cloudflare部署 | 如何更新? |
| ----------------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------- | | ----------------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------- |
| [部署简易教程](https://buycoffee.top/blog/tech/nezha) | [Docker 部署教程](https://buycoffee.top/blog/tech/nezha-docker) | [Cloudflare 部署教程](https://buycoffee.top/blog/tech/nezha-cloudflare) | [更新教程](https://buycoffee.top/blog/tech/nezha-upgrade) | | [部署简易教程](https://buycoffee.top/blog/tech/nezha) | [Docker 部署教程](https://buycoffee.top/blog/tech/nezha-docker) | [Cloudflare 部署教程](https://buycoffee.top/blog/tech/nezha-cloudflare) | [更新教程](https://buycoffee.top/blog/tech/nezha-upgrade) |
| [Vercel-demo](https://nezha-dash-ruddy.vercel.app) | [Docker-demo](https://nezha-docker.buycoffee.tech) | [Cloudflare-demo](https://nezha-cloudflare.buycoffee.tech) | | [Vercel-demo](https://nezha-vercel.buycoffee.top) | [Docker-demo](https://nezha-docker.buycoffee.tech) | [Cloudflare-demo](https://nezha-cloudflare.buycoffee.tech) |
#### 环境变量 #### 环境变量
| 变量名 | 含义 | 示例 | | 变量名 | 含义 | 示例 |
| ------------------------------ | -------------------------------- | ------------------------------------------------------------- | | ------------------------------ | ------------------------ | ------------------------------------------------------------- |
| 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 |
| DefaultLocale | 面板默认显示语言(代码参考下表) | **默认**en | | SitePassword | 页面密码 | 123456 |
| ForceShowAllServers | 是否强制显示所有服务器 | **默认**false | | DefaultLocale | 面板默认显示语言 | **默认**en [简中:zh 繁中:zh-t 英语:en 日语:ja] |
| NEXT_PUBLIC_NezhaFetchInterval | 获取数据间隔(毫秒) | **默认**2000 | | ForceShowAllServers | 是否强制显示所有服务器 | **默认**false |
| NEXT_PUBLIC_ShowFlag | 是否显示旗帜 | **默认**false | | NEXT_PUBLIC_NezhaFetchInterval | 获取数据间隔(毫秒) | **默认**2000 |
| NEXT_PUBLIC_DisableCartoon | 是否禁用卡通人物 | **默认**false | | NEXT_PUBLIC_ShowFlag | 是否显示旗帜 | **默认**false |
| NEXT_PUBLIC_ShowTag | 是否显示标签 | **默认**false | | NEXT_PUBLIC_DisableCartoon | 是否禁用卡通人物 | **默认**false |
| NEXT_PUBLIC_ShowNetTransfer | 是否显示流量信息 | **默认**false | | NEXT_PUBLIC_ShowTag | 是否显示标签 | **默认**false |
| NEXT_PUBLIC_ForceUseSvgFlag | 是否强制使用SVG旗帜 | **默认**false | | NEXT_PUBLIC_ShowNetTransfer | 是否显示流量信息 | **默认**false |
| NEXT_PUBLIC_CustomLogo | 自定义Logo | **示例**https://nezha-cf.buycoffee.top/apple-touch-icon.png | | NEXT_PUBLIC_ForceUseSvgFlag | 是否强制使用SVG旗帜 | **默认**false |
| NEXT_PUBLIC_CustomTitle | 自定义标题 | | | NEXT_PUBLIC_CustomLogo | 自定义Logo | **示例**https://nezha-cf.buycoffee.top/apple-touch-icon.png |
| NEXT_PUBLIC_CustomDescription | 自定义描述(无多语言支持) | | | NEXT_PUBLIC_CustomTitle | 自定义标题 | |
| NEXT_PUBLIC_CustomDescription | 自定义描述(无多语言支持) | |
#### 多语言支持
| 语言 | 代码 | 是否完成翻译 |
| -------- | ---- | ------------ |
| 简体中文 | zh | 是 |
| 繁体中文 | zh-t | 是 |
| 英语 | en | 是 |
| 日语 | ja | 是 |
![screen](/.github/shot-1.png) ![screen](/.github/shot-1.png)
![screen](/.github/shot-2.png) ![screen](/.github/shot-2.png)

View File

@ -1,5 +1,7 @@
"use client"; "use client";
import { ServerDetailChartLoading } from "@/app/[locale]/(main)/ClientComponents/ServerDetailLoading";
import { NezhaAPISafe } from "@/app/[locale]/types/nezha-api";
import AnimatedCircularProgressBar from "@/components/ui/animated-circular-progress-bar"; import AnimatedCircularProgressBar from "@/components/ui/animated-circular-progress-bar";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { ChartConfig, ChartContainer } from "@/components/ui/chart"; import { ChartConfig, ChartContainer } from "@/components/ui/chart";
@ -18,8 +20,6 @@ import {
} from "recharts"; } from "recharts";
import useSWR from "swr"; import useSWR from "swr";
import { NezhaAPISafe } from "../../types/nezha-api";
type cpuChartData = { type cpuChartData = {
timeStamp: string; timeStamp: string;
cpu: number; cpu: number;
@ -83,7 +83,7 @@ export default function ServerDetailChartClient({
</> </>
); );
} }
if (!data) return null; if (!data) return <ServerDetailChartLoading />;
return ( return (
<section className="grid md:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-3"> <section className="grid md:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-3">

View File

@ -1,5 +1,6 @@
"use client"; "use client";
import { ServerDetailLoading } from "@/app/[locale]/(main)/ClientComponents/ServerDetailLoading";
import { NezhaAPISafe } from "@/app/[locale]/types/nezha-api"; import { NezhaAPISafe } from "@/app/[locale]/types/nezha-api";
import { BackIcon } from "@/components/Icon"; import { BackIcon } from "@/components/Icon";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@ -10,8 +11,6 @@ import { useLocale, useTranslations } from "next-intl";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
import ServerDetailLoading from "./ServerDetailLoading";
export default function ServerDetailClient({ export default function ServerDetailClient({
server_id, server_id,
}: { }: {

View File

@ -1,25 +1,11 @@
import { BackIcon } from "@/components/Icon"; import { BackIcon } from "@/components/Icon";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { useLocale } from "next-intl"; import { useLocale } from "next-intl";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
export default function ServerDetailLoading() { export function ServerDetailChartLoading() {
const router = useRouter();
const locale = useLocale();
return ( return (
<div> <div>
<div
onClick={() => {
router.push(`/${locale}/`);
}}
className="flex flex-none cursor-pointer font-semibold leading-none items-center break-all tracking-tight gap-0.5 text-xl"
>
<BackIcon />
<Skeleton className="h-[20px] w-24 rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
</div>
<Skeleton className="flex flex-wrap gap-2 h-[100px] w-1/2 mt-3 rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
<Separator className="my-4" />
<section className="grid md:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-3"> <section className="grid md:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-3">
<Skeleton className="h-[182px] w-full rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton> <Skeleton className="h-[182px] w-full rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
<Skeleton className="h-[182px] w-full rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton> <Skeleton className="h-[182px] w-full rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
@ -31,3 +17,23 @@ export default function ServerDetailLoading() {
</div> </div>
); );
} }
export function ServerDetailLoading() {
const router = useRouter();
const locale = useLocale();
return (
<>
<div
onClick={() => {
router.push(`/${locale}/`);
}}
className="flex flex-none cursor-pointer font-semibold leading-none items-center break-all tracking-tight gap-0.5 text-xl"
>
<BackIcon />
<Skeleton className="h-[20px] w-24 rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
</div>
<Skeleton className="flex flex-wrap gap-2 h-[81px] w-1/2 mt-3 rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
</>
);
}

View File

@ -3,6 +3,7 @@
import { LanguageSwitcher } from "@/components/LanguageSwitcher"; import { LanguageSwitcher } from "@/components/LanguageSwitcher";
import { ModeToggle } from "@/components/ThemeSwitcher"; import { ModeToggle } from "@/components/ThemeSwitcher";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import getEnv from "@/lib/env-entry"; import getEnv from "@/lib/env-entry";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
@ -20,7 +21,6 @@ function Header() {
const router = useRouter(); const router = useRouter();
const locale = useLocale(); const locale = useLocale();
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">
@ -63,7 +63,7 @@ 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;
}); });
@ -96,9 +96,9 @@ function Overview() {
<p className="text-sm font-medium opacity-50"> <p className="text-sm font-medium opacity-50">
{t("p_2390-2457_wherethetimeis")} {t("p_2390-2457_wherethetimeis")}
</p> </p>
{mouted && ( {mouted ? (
<p className="opacity-1 text-sm font-medium">{timeString}</p> <p className="opacity-1 text-sm font-medium">{timeString}</p>
)} ) : <Skeleton className="h-[20px] w-[50px] rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>}
</div> </div>
</section> </section>
); );

View File

@ -1,20 +1,23 @@
import Footer from "@/app/[locale]/(main)/footer"; import Footer from "@/app/[locale]/(main)/footer";
import Header from "@/app/[locale]/(main)/header"; import Header from "@/app/[locale]/(main)/header";
import { auth } from "@/auth"; import { auth } from "@/auth";
import { SignIn } from "@/components/sign-in";
import getEnv from "@/lib/env-entry"; import getEnv from "@/lib/env-entry";
import React from "react";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { getLocale } from "next-intl/server"; import React from "react";
type DashboardProps = { type DashboardProps = {
children: React.ReactNode; children: React.ReactNode;
}; };
export default async function MainLayout({ children }: DashboardProps) { export default async function MainLayout({ children }: DashboardProps) {
const session = await auth() const session = await auth();
const locale = await getLocale()
if (!session && getEnv("SITE_PASSWORD")) { if (!session && getEnv("SitePassword")) {
redirect(`/${locale}/login`); if (getEnv("CF_PAGES")) {
redirect("/api/auth/signin");
} else {
return <SignIn />;
}
} }
return ( return (

View File

@ -1,4 +1,5 @@
// @auto-i18n-check. Please do not delete the line. // @auto-i18n-check. Please do not delete the line.
import { auth } from "@/auth";
import { locales } from "@/i18n-metadata"; import { locales } from "@/i18n-metadata";
import getEnv from "@/lib/env-entry"; import getEnv from "@/lib/env-entry";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@ -13,7 +14,6 @@ import { Inter as FontSans } from "next/font/google";
import React from "react"; import React from "react";
import "/node_modules/flag-icons/css/flag-icons.min.css"; import "/node_modules/flag-icons/css/flag-icons.min.css";
import { auth } from "@/auth";
const fontSans = FontSans({ const fontSans = FontSans({
subsets: ["latin"], subsets: ["latin"],
@ -41,6 +41,11 @@ export const viewport: Viewport = {
userScalable: false, userScalable: false,
}; };
// optimization: force static for vercel
export const dynamic = process.env.VERCEL ? "force-static" : "auto";
export const runtime = "edge";
export async function generateStaticParams() { export async function generateStaticParams() {
return locales.map((locale) => ({ locale })); return locales.map((locale) => ({ locale }));
} }

View File

@ -2,7 +2,7 @@ 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"); const t = useTranslations("NotFoundPage");

View File

@ -1,8 +1,10 @@
import { NezhaAPISafe } from "@/app/[locale]/types/nezha-api"; import { NezhaAPISafe } from "@/app/[locale]/types/nezha-api";
import { auth } from "@/auth";
import getEnv from "@/lib/env-entry";
import { GetServerDetail } from "@/lib/serverFetch"; import { GetServerDetail } from "@/lib/serverFetch";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { auth } from "@/auth"
import getEnv from "@/lib/env-entry"; export const runtime = 'edge';
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export const runtime = 'edge'; export const runtime = 'edge';
@ -13,8 +15,7 @@ interface NezhaDataResponse {
} }
export const GET = auth(async function GET(req) { export const GET = auth(async function GET(req) {
if (!req.auth && getEnv("SitePassword")) {
if (!req.auth && getEnv("SITE_PASSWORD")) {
return NextResponse.json({ message: "Not authenticated" }, { status: 401 }); return NextResponse.json({ message: "Not authenticated" }, { status: 401 });
} }

View File

@ -4,6 +4,8 @@ import getEnv from "@/lib/env-entry";
import { GetServerMonitor } from "@/lib/serverFetch"; import { GetServerMonitor } from "@/lib/serverFetch";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
export const runtime = "edge";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export const runtime = 'edge'; export const runtime = 'edge';
@ -13,8 +15,7 @@ interface NezhaDataResponse {
} }
export const GET = auth(async function GET(req) { export const GET = auth(async function GET(req) {
if (!req.auth && getEnv("SitePassword")) {
if (!req.auth && getEnv("SITE_PASSWORD")) {
return NextResponse.json({ message: "Not authenticated" }, { status: 401 }); return NextResponse.json({ message: "Not authenticated" }, { status: 401 });
} }

View File

@ -1,13 +1,12 @@
import { NezhaAPI, ServerApi } from "@/app/[locale]/types/nezha-api"; import { NezhaAPI, ServerApi } from "@/app/[locale]/types/nezha-api";
import { auth } from "@/auth";
import { MakeOptional } from "@/app/[locale]/types/utils"; import { MakeOptional } from "@/app/[locale]/types/utils";
import { auth } from "@/auth";
import getEnv from "@/lib/env-entry"; import getEnv from "@/lib/env-entry";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export const runtime = 'edge'; export const runtime = "edge";
interface NezhaDataResponse { interface NezhaDataResponse {
error?: string; error?: string;
@ -15,8 +14,7 @@ interface NezhaDataResponse {
} }
export const GET = auth(async function GET(req) { export const GET = auth(async function GET(req) {
if (!req.auth && getEnv("SitePassword")) {
if (!req.auth && getEnv("SITE_PASSWORD")) {
return NextResponse.json({ message: "Not authenticated" }, { status: 401 }); return NextResponse.json({ message: "Not authenticated" }, { status: 401 });
} }

17
auth.ts
View File

@ -1,20 +1,21 @@
import NextAuth from "next-auth" import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials" import Credentials from "next-auth/providers/credentials";
import getEnv from "./lib/env-entry"
import getEnv from "./lib/env-entry";
export const { handlers, signIn, signOut, auth } = NextAuth({ export const { handlers, signIn, signOut, auth } = NextAuth({
secret:"this_is_nezha_dash_web_secret", secret: "this_is_nezha_dash_web_secret",
providers: [ providers: [
Credentials({ Credentials({
credentials: { credentials: {
password: {}, password: {},
}, },
authorize: async (credentials) => { authorize: async (credentials) => {
if (credentials.password === getEnv("SITE_PASSWORD")) { if (credentials.password === getEnv("SitePassword")) {
return { id: "0" } return { id: "0" };
} }
return null return null;
}, },
}), }),
], ],
}) });

View File

@ -1,42 +1,47 @@
import Footer from "@/app/[locale]/(main)/footer" import Footer from "@/app/[locale]/(main)/footer";
import Header from "@/app/[locale]/(main)/header" import Header from "@/app/[locale]/(main)/header";
import { signIn } from "@/auth" import { signIn } from "@/auth";
import { useLocale } from "next-intl" import { useLocale } from "next-intl";
import { redirect } from "next/navigation" import { redirect } from "next/navigation";
export const runtime = 'edge'; export const runtime = 'edge';
export function SignIn() { export function SignIn() {
const locale = useLocale() const locale = useLocale();
async function handleSubmit(formData: FormData) {
async function handleSubmit(formData: FormData) { "use server";
'use server' try {
try { await signIn("credentials", formData);
await signIn("credentials", formData) } catch (error) {
} catch (error) { redirect(`/${locale}`);
redirect(`/${locale}`)
}
} }
}
return ( return (
<div className="flex min-h-screen w-full flex-col"> <div className="flex min-h-screen w-full flex-col">
<main className="flex min-h-[calc(100vh_-_theme(spacing.16))] flex-1 flex-col gap-4 bg-muted/40 p-4 md:p-10 md:pt-8"> <main className="flex min-h-[calc(100vh_-_theme(spacing.16))] flex-1 flex-col gap-4 bg-muted/40 p-4 md:p-10 md:pt-8">
<Header /> <Header />
<form <form
className="flex flex-col items-center justify-start gap-4 p-4 " className="flex flex-col items-center justify-start gap-4 p-4 "
action={handleSubmit} action={handleSubmit}
> >
<section className="flex flex-col items-start gap-2"> <section className="flex flex-col items-start gap-2">
<label className="flex flex-col items-start gap-1 "> <label className="flex flex-col items-start gap-1 ">
<p className="text-base font-semibold"></p> <p className="text-base font-semibold"></p>
<input className="px-1 border-[1px] rounded-[5px]" name="password" type="password" /> <input
</label> className="px-1 border-[1px] rounded-[5px]"
<button className=" px-1.5 py-0.5 w-fit text-sm font-semibold rounded-[8px] border bg-card hover:brightness-95 transition-all text-card-foreground shadow-lg shadow-neutral-200/40 dark:shadow-none"></button> name="password"
</section> type="password"
</form> />
<Footer /> </label>
</main> <button className=" px-1.5 py-0.5 w-fit text-sm font-semibold rounded-[8px] border bg-card hover:brightness-95 transition-all text-card-foreground shadow-lg shadow-neutral-200/40 dark:shadow-none">
</div>
) </button>
</section>
</form>
<Footer />
</main>
</div>
);
} }

View File

@ -1,10 +0,0 @@
version: "3"
services:
nezha-dash:
container_name: nezha-dash
image: registry.cn-guangzhou.aliyuncs.com/hamster-home/nezha-dash:latest
volumes:
- ./.env:/app/.env
restart: always
ports:
- "4123:3000"

View File

@ -25,7 +25,7 @@ const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
experimental: { experimental: {
serverActions: { serverActions: {
allowedOrigins: ['*'], allowedOrigins: ["*"],
}, },
}, },
logging: { logging: {