Merge branch 'main' into cloudflare-dev

This commit is contained in:
hamster1963 2024-09-30 23:48:18 +08:00
commit 74dbba7881
17 changed files with 244 additions and 75 deletions

View File

@ -1,6 +1,6 @@
NezhaBaseUrl=http://1.1.1.1:8008
NezhaAuth=nezha-token
ServerDisablePrefetch=false
NEXT_PUBLIC_NezhaFetchInterval=2000
NezhaBaseUrl=http://124.XX.XX.XX:8008
NezhaAuth=your-nezha-api-token
DefaultLocale=zh
NEXT_PUBLIC_NezhaFetchInterval=5000
NEXT_PUBLIC_ShowFlag=true
NEXT_PUBLIC_DisableCartoon=false
NEXT_PUBLIC_DisableCartoon=true

View File

@ -5,8 +5,40 @@ on:
tags:
- "v*"
env:
REGISTRY_IMAGE: hamster1963/nezha-dash
ALIYUN_REGISTRY_IMAGE: registry.cn-guangzhou.aliyuncs.com/hamster-home/nezha-dash
jobs:
changelog:
name: Generate Changelog
runs-on: ubuntu-latest
outputs:
release_body: ${{ steps.git-cliff.outputs.content }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate a changelog
uses: orhun/git-cliff-action@v4
id: git-cliff
with:
config: git-cliff-config/cliff.toml
args: -vv --latest --strip 'footer'
env:
OUTPUT: CHANGES.md
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
body: ${{ steps.git-cliff.outputs.content }}
token: ${{ secrets.GITHUB_TOKEN }}
env:
GITHUB_REPOSITORY: ${{ github.repository }}
build-and-push:
name: Build and push Docker image
runs-on: ubuntu-latest
environment: Production
steps:
@ -37,8 +69,8 @@ jobs:
uses: docker/metadata-action@v5
with:
images: |
hamster1963/nezha-dash
registry.cn-guangzhou.aliyuncs.com/hamster-home/nezha-dash
${{ env.REGISTRY_IMAGE }}
${{ env.ALIYUN_REGISTRY_IMAGE }}
tags: |
type=raw,value=latest
type=ref,event=tag

View File

@ -13,10 +13,10 @@
#### 环境变量
| 变量名 | 含义 | 示例 |
| ------------------------------ | -------------------- | -------------------------------- |
| ------------------------------ | -------------------------------- | -------------------------------- |
| NezhaBaseUrl | nezha 面板地址 | http://120.x.x.x:8008 |
| NezhaAuth | nezha 面板 API Token | 5hAY3QX6Nl9B3Uxxxx26KMvOMyXS1Udi |
| ServerDisablePrefetch | 是否禁用预加载 | **默认**false |
| DefaultLocale | 面板默认显示语言(代码参考下表) | **默认**en |
| NEXT_PUBLIC_NezhaFetchInterval | 获取数据间隔(毫秒) | **默认**2000 |
| NEXT_PUBLIC_ShowFlag | 是否显示旗帜 | **默认**false |
| NEXT_PUBLIC_DisableCartoon | 是否禁用卡通人物 | **默认**false |

View File

@ -6,18 +6,29 @@ import { nezhaFetcher } from "../../../../lib/utils";
import useSWR from "swr";
import getEnv from "../../../../lib/env-entry";
export default function ServerListClient() {
const { data } = useSWR<ServerApi>("/api/server", nezhaFetcher, {
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">
<p className="text-sm font-medium opacity-40">{error.message}</p>
<p className="text-sm font-medium opacity-40">
Please check your environment variables and review the server console
logs for more details.
</p>
</div>
);
if (!data) return null;
const sortedServers = data.result.sort((a, b) => {
if (a.display_index && b.display_index) {
return b.display_index - a.display_index;
}
if (a.display_index) return -1;
if (b.display_index) return 1;
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;
});
return (
<section className="grid grid-cols-1 gap-2 md:grid-cols-2">
{sortedServers.map((serverInfo) => (

View File

@ -1,9 +1,15 @@
import { unstable_setRequestLocale } from "next-intl/server";
import ServerList from "../../../components/ServerList";
import ServerOverview from "../../../components/ServerOverview";
export const runtime = 'edge';
export default function Home() {
export default function Home({
params: { locale },
}: {
params: { locale: string };
}) {
unstable_setRequestLocale(locale);
return (
<div className="mx-auto grid w-full max-w-5xl gap-4 md:gap-6">
<ServerOverview />

View File

@ -1,6 +1,8 @@
// @auto-i18n-check. Please do not delete the line.
import "@/styles/globals.css";
import "/node_modules/flag-icons/css/flag-icons.min.css";
import React from "react";
import { NextIntlClientProvider, useMessages } from "next-intl";
import { PublicEnvScript } from "next-runtime-env";
@ -10,6 +12,7 @@ import { ThemeProvider } from "next-themes";
import { Viewport } from "next";
import { cn } from "@/lib/utils";
import { locales } from "@/i18n-metadata";
import { unstable_setRequestLocale } from "next-intl/server";
const fontSans = FontSans({
subsets: ["latin"],
@ -34,11 +37,12 @@ export const viewport: Viewport = {
userScalable: false,
};
export function generateStaticParams() {
export const dynamic = "force-static";
export async function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
export default function LocaleLayout({
children,
params: { locale },
@ -46,6 +50,8 @@ export default function LocaleLayout({
children: React.ReactNode;
params: { locale: string };
}) {
unstable_setRequestLocale(locale);
const messages = useMessages();
return (
<html lang={locale} suppressHydrationWarning>

View File

@ -8,17 +8,18 @@ export const dynamic = "force-dynamic";
export const runtime = 'edge';
interface NezhaDataResponse {
error?: string;
data?: ServerApi;
}
export async function GET(_: Request) {
try {
const response = await GetNezhaData();
return NextResponse.json(response, { status: 200 });
} catch (error) {
console.error(error);
return NextResponse.json(
{ error: "fetch nezha data failed" },
{ status: 400 },
);
const response = (await GetNezhaData()) as NezhaDataResponse;
if (response.error) {
console.log(response.error);
return NextResponse.json({ error: response.error }, { status: 400 });
}
return NextResponse.json(response, { status: 200 });
}
async function GetNezhaData() {

BIN
bun.lockb

Binary file not shown.

View File

@ -9,8 +9,9 @@ import {
} from "@/components/ui/popover";
import { cn, formatNezhaInfo } from "@/lib/utils";
import ServerCardPopover from "./ServerCardPopover";
import getUnicodeFlagIcon from "country-flag-icons/unicode";
import { env } from "next-runtime-env";
import ServerFlag from "./ServerFlag";
export default function ServerCard({
serverInfo,
@ -32,15 +33,7 @@ export default function ServerCard({
<Popover>
<PopoverTrigger asChild>
<section className={"flex items-center justify-start gap-2 lg:w-28"}>
{showFlag ? (
country_code ? (
<span className="text-[12px] text-muted-foreground">
{getUnicodeFlagIcon(country_code)}
</span>
) : (
<span className="text-[12px] text-muted-foreground">🏁</span>
)
) : null}
{showFlag ? <ServerFlag country_code={country_code} /> : null}
<p
className={cn(
"break-all font-bold tracking-tight",
@ -58,8 +51,6 @@ export default function ServerCard({
</Popover>
<section className={"grid grid-cols-5 items-center gap-3"}>
<div className={"flex w-14 flex-col"}>
{" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("CPU")}</p>
<div className="flex items-center text-xs font-semibold">
{cpu.toFixed(2)}%
@ -67,8 +58,6 @@ export default function ServerCard({
<ServerUsageBar value={cpu} />
</div>
<div className={"flex w-14 flex-col"}>
{" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("Mem")}</p>
<div className="flex items-center text-xs font-semibold">
{mem.toFixed(2)}%
@ -76,8 +65,6 @@ export default function ServerCard({
<ServerUsageBar value={mem} />
</div>
<div className={"flex w-14 flex-col"}>
{" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("STG")}</p>
<div className="flex items-center text-xs font-semibold">
{stg.toFixed(2)}%
@ -85,8 +72,6 @@ export default function ServerCard({
<ServerUsageBar value={stg} />
</div>
<div className={"flex w-14 flex-col"}>
{" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("Upload")}</p>
<div className="flex items-center text-xs font-semibold">
{up.toFixed(2)}
@ -94,8 +79,6 @@ export default function ServerCard({
</div>
</div>
<div className={"flex w-14 flex-col"}>
{" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("Download")}</p>
<div className="flex items-center text-xs font-semibold">
{down.toFixed(2)}
@ -113,15 +96,7 @@ export default function ServerCard({
<Popover>
<PopoverTrigger asChild>
<section className={"flex items-center justify-start gap-2 lg:w-28"}>
{showFlag ? (
country_code ? (
<span className="text-[12px] text-muted-foreground">
{getUnicodeFlagIcon(country_code)}
</span>
) : (
<span className="text-[12px] text-muted-foreground">🏁</span>
)
) : null}
{showFlag ? <ServerFlag country_code={country_code} /> : null}
<p
className={cn(
"break-all font-bold tracking-tight",

36
components/ServerFlag.tsx Normal file
View File

@ -0,0 +1,36 @@
import { useEffect, useState } from "react";
import getUnicodeFlagIcon from "country-flag-icons/unicode";
export default function ServerFlag({ country_code }: { country_code: string }) {
const [supportsEmojiFlags, setSupportsEmojiFlags] = useState(false);
useEffect(() => {
const checkEmojiSupport = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const emojiFlag = "🇺🇸"; // 使用美国国旗作为测试
if (!ctx) return;
ctx.fillStyle = "#000";
ctx.textBaseline = "top";
ctx.font = "32px Arial";
ctx.fillText(emojiFlag, 0, 0);
const support = ctx.getImageData(16, 16, 1, 1).data[3] !== 0;
setSupportsEmojiFlags(support);
};
checkEmojiSupport();
}, []);
if (!country_code) return null;
return (
<span className="text-[12px] text-muted-foreground">
{!supportsEmojiFlags ? (
<span className={`fi fi-${country_code}`}></span>
) : (
getUnicodeFlagIcon(country_code)
)}
</span>
);
}

View File

@ -1,6 +1,6 @@
NezhaBaseUrl=http://0.0.0.0:8008
NezhaAuth=5hAY3QX6Nl9B3UOQgB26KdsdS1dsdUdM
ServerDisablePrefetch=false
NezhaBaseUrl=http://124.XX.XX.XX:8008
NezhaAuth=your-nezha-api-token
DefaultLocale=zh
NEXT_PUBLIC_NezhaFetchInterval=5000
NEXT_PUBLIC_ShowFlag=true
NEXT_PUBLIC_DisableCartoon=true

View File

@ -0,0 +1,83 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration
#
# Lines starting with "#" are comments.
# Configuration options are organized into tables and keys.
# See documentation for more information on available options.
[changelog]
# changelog header
header = """
# Changelog\n
"""
# template for the changelog body
# https://tera.netlify.app/docs/
body = """
{% if version %}\
## Release {{ version | trim_start_matches(pat="v") }} - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
* {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]($REPO/commit/{{ commit.id }})) by [@{{ commit.author.name }}](https://github.com/{{ commit.author.name }})\
{% for footer in commit.footers -%}
, {{ footer.token }}{{ footer.separator }}{{ footer.value }}\
{% endfor %}\
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
postprocessors = [
{ pattern = '\$REPO', replace = "https://github.com/hamster1963/nezha-dash" }, # replace repository URL
]
# changelog footer
footer = """
"""
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = true
# regex for preprocessing the commit messages
commit_preprocessors = [
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))" },
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->⛰️ Features" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore\\(pull\\)", skip = true },
{ message = "^chore", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
{ message = "^update", group = "<!-- 9 -->🔼 Updates" },
{ message = "^delete", group = "<!-- 10 -->🔞 Delete" },
]
# protect breaking changes from being skipped due to matching a skipping commit_parser
protect_breaking_commits = false
# filter out the commits that are not matched by commit parsers
filter_commits = false
# glob pattern for matching git tags
tag_pattern = "v[0-20]*"
# regex for skipping tags
skip_tags = ""
# regex for ignoring tags
ignore_tags = "v.*-beta.*"
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"

View File

@ -1,5 +1,7 @@
// @auto-i18n-check. Please do not delete the line.
import getEnv from "./lib/env-entry";
export const localeItems = [
{ code: "en", name: "English" },
{ code: "ja", name: "日本語" },
@ -21,4 +23,4 @@ export const localeItems = [
];
export const locales = localeItems.map((item) => item.code);
export const defaultLocale = "en";
export const defaultLocale = getEnv("DefaultLocale") || "en";

View File

@ -10,8 +10,8 @@ export async function GetNezhaData() {
var nezhaBaseUrl = getEnv("NezhaBaseUrl");
if (!nezhaBaseUrl) {
console.error("NezhaBaseUrl is not set");
throw new Error("NezhaBaseUrl is not set");
console.log("NezhaBaseUrl is not set");
return { error: "NezhaBaseUrl is not set" };
}
// Remove trailing slash
@ -27,7 +27,12 @@ export async function GetNezhaData() {
revalidate: 0,
},
});
const nezhaData = (await response.json()).result as NezhaAPI[];
const resData = await response.json();
const nezhaData = resData.result as NezhaAPI[];
if (!nezhaData) {
console.log(resData);
return { error: "NezhaData fetch failed" };
}
const data: ServerApi = {
live_servers: 0,
offline_servers: 0,

View File

@ -1,14 +1,14 @@
// @auto-i18n-check. Please do not delete the line.
import createMiddleware from "next-intl/middleware";
import { locales } from "./i18n-metadata";
import { defaultLocale, locales } from "./i18n-metadata";
export default createMiddleware({
// A list of all locales that are supported
locales: locales,
// Used when no locale matches
defaultLocale: "en",
defaultLocale: defaultLocale,
// '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

View File

@ -1,3 +1,9 @@
import withBundleAnalyzer from "@next/bundle-analyzer";
const bundleAnalyzer = withBundleAnalyzer({
enabled: process.env.ANALYZE === "true",
});
import createNextIntlPlugin from "next-intl/plugin";
const withNextIntl = createNextIntlPlugin();
import withPWAInit from "@ducanh2912/next-pwa";
@ -16,5 +22,10 @@ const withPWA = withPWAInit({
const nextConfig = {
output: "standalone",
reactStrictMode: true,
logging: {
fetches: {
fullUrl: true,
},
},
};
export default withPWA(withNextIntl(nextConfig));
export default bundleAnalyzer(withPWA(withNextIntl(nextConfig)));

View File

@ -27,6 +27,7 @@
"clsx": "^2.1.1",
"country-flag-icons": "^1.5.13",
"eslint-plugin-simple-import-sort": "^12.1.1",
"flag-icons": "^7.2.3",
"lucide-react": "^0.414.0",
"luxon": "^3.5.0",
"next": "^14.2.13",
@ -47,8 +48,8 @@
"eslint-plugin-turbo": "^2.1.2",
"eslint-plugin-unused-imports": "^4.1.4",
"@next/bundle-analyzer": "^14.2.13",
"@types/node": "^22.7.2",
"@types/react": "^18.3.9",
"@types/node": "^22.7.4",
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"autoprefixer": "^10.4.20",
"eslint": "^9.11.1",