Compare commits

...

8 Commits

Author SHA1 Message Date
hamster1963
c0e7d6ab2b docs: new images 2024-11-20 18:02:51 +08:00
hamster1963
a4459255b1 Merge branch 'main' into cloudflare 2024-11-20 17:45:01 +08:00
hamster1963
ffe36efccc update: v1.4.2-fix 2024-11-20 17:42:17 +08:00
hamster1963
9e3945efff fix(global): point radius 2024-11-20 17:41:59 +08:00
hamster1963
da0be4d6f5 Merge branch 'main' into cloudflare 2024-11-20 17:33:32 +08:00
hamster1963
635cc47176 update: v1.4.2 2024-11-20 17:33:15 +08:00
hamster1963
4a2971e309 feat(overview): sort by network traffic 2024-11-20 17:32:34 +08:00
hamster1963
20983bb692 feat(global): light up the world! 2024-11-20 17:03:32 +08:00
27 changed files with 539 additions and 239 deletions

BIN
.github/1-dark.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
.github/1.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

BIN
.github/2-dark.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

BIN
.github/2.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

BIN
.github/3-dark.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
.github/3.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
.github/4-dark.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

BIN
.github/4.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 604 KiB

BIN
.github/shot-1.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 605 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 754 KiB

BIN
.github/shot-2.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 750 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 KiB

BIN
.github/shot-3.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 579 KiB

BIN
.github/shotOne.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 KiB

BIN
.github/shotTwo.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 596 KiB

View File

@ -24,9 +24,11 @@
[环境变量介绍](https://nezhadash-docs.vercel.app/environment) [环境变量介绍](https://nezhadash-docs.vercel.app/environment)
![screen](/.github/shot-1.png) ![screen](/.github/1.webp)
![screen](/.github/shot-2.png) ![screen](/.github/2.webp)
![screen](/.github/shot-3.png) ![screen](/.github/3.webp)
![screen](/.github/shot-1-dark.png) ![screen](/.github/4.webp)
![screen](/.github/shot-2-dark.png) ![screen](/.github/1-dark.webp)
![screen](/.github/shot-3-dark.png) ![screen](/.github/2-dark.webp)
![screen](/.github/3-dark.webp)
![screen](/.github/4-dark.webp)

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,7 @@ 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 { useFilter } from "@/lib/network-filter-context";
import { useStatus } from "@/lib/status-context"; import { useStatus } from "@/lib/status-context";
import { nezhaFetcher } from "@/lib/utils"; import { nezhaFetcher } from "@/lib/utils";
import { GlobeAsiaAustraliaIcon } from "@heroicons/react/20/solid"; import { GlobeAsiaAustraliaIcon } from "@heroicons/react/20/solid";
@ -14,6 +15,7 @@ import useSWR from "swr";
export default function ServerListClient() { export default function ServerListClient() {
const { status, setStatus } = useStatus(); const { status, setStatus } = useStatus();
const { filter, setFilter } = useFilter();
const t = useTranslations("ServerListClient"); const t = useTranslations("ServerListClient");
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const defaultTag = "defaultTag"; const defaultTag = "defaultTag";
@ -96,6 +98,17 @@ export default function ServerListClient() {
? filteredServersByStatus ? filteredServersByStatus
: filteredServersByStatus.filter((server) => server.tag === tag); : filteredServersByStatus.filter((server) => server.tag === tag);
if (filter) {
// 根据使用流量进行从高到低排序
filteredServers.sort((a, b) => {
return (
b.status.NetInTransfer +
b.status.NetOutTransfer -
(a.status.NetInTransfer + b.status.NetOutTransfer)
);
});
}
const tagCountMap: Record<string, number> = {}; const tagCountMap: Record<string, number> = {};
filteredServersByStatus.forEach((server) => { filteredServersByStatus.forEach((server) => {
if (server.tag) { if (server.tag) {
@ -109,6 +122,7 @@ export default function ServerListClient() {
<button <button
onClick={() => { onClick={() => {
setStatus("all"); setStatus("all");
setFilter(false);
router.push(`/?global=true`); router.push(`/?global=true`);
}} }}
className="rounded-[50px] bg-stone-100 p-[10px] transition-all hover:bg-stone-200 dark:hover:bg-stone-700 dark:bg-stone-800" className="rounded-[50px] bg-stone-100 p-[10px] transition-all hover:bg-stone-200 dark:hover:bg-stone-700 dark:bg-stone-800"

View File

@ -4,6 +4,7 @@ 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 { useFilter } from "@/lib/network-filter-context";
import { useStatus } from "@/lib/status-context"; import { useStatus } from "@/lib/status-context";
import { cn, formatBytes, nezhaFetcher } from "@/lib/utils"; import { cn, formatBytes, nezhaFetcher } from "@/lib/utils";
import blogMan from "@/public/blog-man.webp"; import blogMan from "@/public/blog-man.webp";
@ -14,6 +15,7 @@ import useSWR from "swr";
export default function ServerOverviewClient() { export default function ServerOverviewClient() {
const { status, setStatus } = useStatus(); const { status, setStatus } = useStatus();
const { filter, setFilter } = useFilter();
const t = useTranslations("ServerOverviewClient"); const t = useTranslations("ServerOverviewClient");
const { data, error, isLoading } = useSWR<ServerApi>( const { data, error, isLoading } = useSWR<ServerApi>(
"/api/server", "/api/server",
@ -40,7 +42,12 @@ export default function ServerOverviewClient() {
<> <>
<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={() => (global ? null : setStatus("all"))} onClick={() => {
setFilter(false);
if (!global) {
setStatus("all");
}
}}
className="cursor-pointer hover:border-blue-500 transition-all" className="cursor-pointer hover:border-blue-500 transition-all"
> >
<CardContent className="px-6 py-3"> <CardContent className="px-6 py-3">
@ -66,7 +73,12 @@ export default function ServerOverviewClient() {
</CardContent> </CardContent>
</Card> </Card>
<Card <Card
onClick={() => (global ? null : setStatus("online"))} onClick={() => {
setFilter(false);
if (!global) {
setStatus("online");
}
}}
className={cn( className={cn(
"cursor-pointer hover:ring-green-500 ring-1 ring-transparent transition-all", "cursor-pointer hover:ring-green-500 ring-1 ring-transparent transition-all",
{ {
@ -98,7 +110,12 @@ export default function ServerOverviewClient() {
</CardContent> </CardContent>
</Card> </Card>
<Card <Card
onClick={() => (global ? null : setStatus("offline"))} onClick={() => {
setFilter(false);
if (!global) {
setStatus("offline");
}
}}
className={cn( className={cn(
"cursor-pointer hover:ring-red-500 ring-1 ring-transparent transition-all", "cursor-pointer hover:ring-red-500 ring-1 ring-transparent transition-all",
{ {
@ -129,7 +146,20 @@ export default function ServerOverviewClient() {
</section> </section>
</CardContent> </CardContent>
</Card> </Card>
<Card> <Card
onClick={() => {
setStatus("all");
if (!global) {
setFilter(true);
}
}}
className={cn(
"cursor-pointer hover:ring-purple-500 ring-1 ring-transparent transition-all",
{
"ring-purple-500 ring-2 border-transparent": filter === true,
},
)}
>
<CardContent className="relative px-6 py-3"> <CardContent className="relative 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">

View File

@ -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 { FilterProvider } from "@/lib/network-filter-context";
import { StatusProvider } from "@/lib/status-context"; import { StatusProvider } from "@/lib/status-context";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import "@/styles/globals.css"; import "@/styles/globals.css";
@ -79,7 +80,9 @@ export default async function LocaleLayout({
disableTransitionOnChange disableTransitionOnChange
> >
<NextIntlClientProvider messages={messages}> <NextIntlClientProvider messages={messages}>
<FilterProvider>
<StatusProvider>{children}</StatusProvider> <StatusProvider>{children}</StatusProvider>
</FilterProvider>
</NextIntlClientProvider> </NextIntlClientProvider>
</ThemeProvider> </ThemeProvider>
</MotionProvider> </MotionProvider>

BIN
bun.lockb

Binary file not shown.

183
lib/geo-json-string.ts Normal file

File diff suppressed because one or more lines are too long

229
lib/geo.ts Normal file
View File

@ -0,0 +1,229 @@
// ISO 3166-1 alpha-2 到 alpha-3 的国家代码映射
export const countryCodeMapping: { [key: string]: string } = {
// 亚洲
AF: "AFG", // 阿富汗
AM: "ARM", // 亚美尼亚
AZ: "AZE", // 阿塞拜疆
BD: "BGD", // 孟加拉国
BH: "BHR", // 巴林
BT: "BTN", // 不丹
BN: "BRN", // 文莱
KH: "KHM", // 柬埔寨
CN: "CHN", // 中国
CY: "CYP", // 塞浦路斯
GE: "GEO", // 格鲁吉亚
IN: "IND", // 印度
ID: "IDN", // 印度尼西亚
IR: "IRN", // 伊朗
IQ: "IRQ", // 伊拉克
IL: "ISR", // 以色列
JP: "JPN", // 日本
JO: "JOR", // 约旦
KZ: "KAZ", // 哈萨克斯坦
KW: "KWT", // 科威特
KG: "KGZ", // 吉尔吉斯斯坦
LA: "LAO", // 老挝
LB: "LBN", // 黎巴嫩
MY: "MYS", // 马来西亚
MV: "MDV", // 马尔代夫
MN: "MNG", // 蒙古
MM: "MMR", // 缅甸
NP: "NPL", // 尼泊尔
OM: "OMN", // 阿曼
PK: "PAK", // 巴基斯坦
PH: "PHL", // 菲律宾
QA: "QAT", // 卡塔尔
SA: "SAU", // 沙特阿拉伯
SG: "SGP", // 新加坡
KR: "KOR", // 韩国
LK: "LKA", // 斯里兰卡
SY: "SYR", // 叙利亚
TW: "TWN", // 台湾
TJ: "TJK", // 塔吉克斯坦
TH: "THA", // 泰国
TR: "TUR", // 土耳其
TM: "TKM", // 土库曼斯坦
AE: "ARE", // 阿联酋
UZ: "UZB", // 乌兹别克斯坦
VN: "VNM", // 越南
YE: "YEM", // 也门
PS: "PSE", // 巴勒斯坦
// 欧洲
AL: "ALB", // 阿尔巴尼亚
AD: "AND", // 安道尔
AT: "AUT", // 奥地利
BY: "BLR", // 白俄罗斯
BE: "BEL", // 比利时
BA: "BIH", // 波黑
BG: "BGR", // 保加利亚
HR: "HRV", // 克罗地亚
CZ: "CZE", // 捷克
DK: "DNK", // 丹麦
EE: "EST", // 爱沙尼亚
FI: "FIN", // 芬兰
FR: "FRA", // 法国
DE: "DEU", // 德国
GR: "GRC", // 希腊
HU: "HUN", // 匈牙利
IS: "ISL", // 冰岛
IE: "IRL", // 爱尔兰
IT: "ITA", // 意大利
LV: "LVA", // 拉脱维亚
LI: "LIE", // 列支敦士登
LT: "LTU", // 立陶宛
LU: "LUX", // 卢森堡
MT: "MLT", // 马耳他
MD: "MDA", // 摩尔多瓦
MC: "MCO", // 摩纳哥
ME: "MNE", // 黑山
NL: "NLD", // 荷兰
NO: "NOR", // 挪威
PL: "POL", // 波兰
PT: "PRT", // 葡萄牙
RO: "ROU", // 罗马尼亚
RU: "RUS", // 俄罗斯
SM: "SMR", // 圣马力诺
RS: "SRB", // 塞尔维亚
SK: "SVK", // 斯洛伐克
SI: "SVN", // 斯洛文尼亚
ES: "ESP", // 西班牙
SE: "SWE", // 瑞典
CH: "CHE", // 瑞士
UA: "UKR", // 乌克兰
GB: "GBR", // 英国
VA: "VAT", // 梵蒂冈
// 北美洲
AG: "ATG", // 安提瓜和巴布达
BS: "BHS", // 巴哈马
BB: "BRB", // 巴巴多斯
BZ: "BLZ", // 伯利兹
CA: "CAN", // 加拿大
CR: "CRI", // 哥斯达黎加
CU: "CUB", // 古巴
DM: "DMA", // 多米尼克
DO: "DOM", // 多米尼加共和国
SV: "SLV", // 萨尔瓦多
GD: "GRD", // 格林纳达
GT: "GTM", // 危地马拉
HT: "HTI", // 海地
HN: "HND", // 洪都拉斯
JM: "JAM", // 牙买加
MX: "MEX", // 墨西哥
NI: "NIC", // 尼加拉瓜
PA: "PAN", // 巴拿马
KN: "KNA", // 圣基茨和尼维斯
LC: "LCA", // 圣卢西亚
VC: "VCT", // 圣文森特和格林纳丁斯
TT: "TTO", // 特立尼达和多巴哥
US: "USA", // 美国
// 南美洲
AR: "ARG", // 阿根廷
BO: "BOL", // 玻利维亚
BR: "BRA", // 巴西
CL: "CHL", // 智利
CO: "COL", // 哥伦比亚
EC: "ECU", // 厄瓜多尔
GY: "GUY", // 圭亚那
PY: "PRY", // 巴拉圭
PE: "PER", // 秘鲁
SR: "SUR", // 苏里南
UY: "URY", // 乌拉圭
VE: "VEN", // 委内瑞拉
// 大洋洲
AU: "AUS", // 澳大利亚
FJ: "FJI", // 斐济
KI: "KIR", // 基里巴斯
MH: "MHL", // 马绍尔群岛
FM: "FSM", // 密克罗尼西亚
NR: "NRU", // 瑙鲁
NZ: "NZL", // 新西兰
PW: "PLW", // 帕劳
PG: "PNG", // 巴布亚新几内亚
WS: "WSM", // 萨摩亚
SB: "SLB", // 所罗门群岛
TO: "TON", // 汤加
TV: "TUV", // 图瓦卢
VU: "VUT", // 瓦努阿图
// 非洲
DZ: "DZA", // 阿尔及利亚
AO: "AGO", // 安哥拉
BJ: "BEN", // 贝宁
BW: "BWA", // 博茨瓦纳
BF: "BFA", // 布基纳法索
BI: "BDI", // 布隆迪
CM: "CMR", // 喀麦隆
CV: "CPV", // 佛得角
CF: "CAF", // 中非共和国
TD: "TCD", // 乍得
KM: "COM", // 科摩罗
CG: "COG", // 刚果
CD: "COD", // 刚果民主共和国
CI: "CIV", // 科特迪瓦
DJ: "DJI", // 吉布提
EG: "EGY", // 埃及
GQ: "GNQ", // 赤道几内亚
ER: "ERI", // 厄立特里亚
ET: "ETH", // 埃塞俄比亚
GA: "GAB", // 加蓬
GM: "GMB", // 冈比亚
GH: "GHA", // 加纳
GN: "GIN", // 几内亚
GW: "GNB", // 几内亚比绍
KE: "KEN", // 肯尼亚
LS: "LSO", // 莱索托
LR: "LBR", // 利比里亚
LY: "LBY", // 利比亚
MG: "MDG", // 马达加斯加
MW: "MWI", // 马拉维
ML: "MLI", // 马里
MR: "MRT", // 毛里塔尼亚
MU: "MUS", // 毛里求斯
MA: "MAR", // 摩洛哥
MZ: "MOZ", // 莫桑比克
NA: "NAM", // 纳米比亚
NE: "NER", // 尼日尔
NG: "NGA", // 尼日利亚
RW: "RWA", // 卢旺达
ST: "STP", // 圣多美和普林西比
SN: "SEN", // 塞内加尔
SC: "SYC", // 塞舌尔
SL: "SLE", // 塞拉利昂
SO: "SOM", // 索马里
ZA: "ZAF", // 南非
SS: "SSD", // 南苏丹
SD: "SDN", // 苏丹
SZ: "SWZ", // 斯威士兰
TZ: "TZA", // 坦桑尼亚
TG: "TGO", // 多哥
TN: "TUN", // 突尼斯
UG: "UGA", // 乌干达
EH: "ESH", // 西撒哈拉
ZM: "ZMB", // 赞比亚
ZW: "ZWE", // 津巴布韦
};
/**
* ISO 3166-1 alpha-2 (2) ISO 3166-1 alpha-3 (3)
* @param alpha2 2
* @returns 3
*/
export function convertToAlpha3(alpha2: string): string {
if (!alpha2) return "";
const code = alpha2.toUpperCase();
return countryCodeMapping[code] || alpha2;
}
/**
* 23
* @param alpha2Codes 2
* @returns 3
*/
export function convertMultipleToAlpha3(alpha2Codes: string[]): string[] {
return alpha2Codes.map((code) => convertToAlpha3(code));
}

1
lib/map-string.ts Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,28 @@
"use client";
import React, { ReactNode, createContext, useContext, useState } from "react";
interface FilterContextType {
filter: boolean;
setFilter: (filter: boolean) => void;
}
const FilterContext = createContext<FilterContextType | undefined>(undefined);
export function FilterProvider({ children }: { children: ReactNode }) {
const [filter, setFilter] = useState<boolean>(false);
return (
<FilterContext.Provider value={{ filter, setFilter }}>
{children}
</FilterContext.Provider>
);
}
export function useFilter() {
const context = useContext(FilterContext);
if (context === undefined) {
throw new Error("useFilter must be used within a FilterProvider");
}
return context;
}

View File

@ -1,6 +1,6 @@
{ {
"name": "nezha-dash", "name": "nezha-dash",
"version": "1.4.1", "version": "1.4.2-fix",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 3040", "dev": "next dev -p 3040",
@ -22,6 +22,7 @@
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@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",
"@turf/turf": "^7.1.0",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/eslint-plugin": "^8.15.0",
"caniuse-lite": "^1.0.30001680", "caniuse-lite": "^1.0.30001680",
@ -55,7 +56,7 @@
"eslint-plugin-turbo": "^2.3.0", "eslint-plugin-turbo": "^2.3.0",
"eslint-plugin-unused-imports": "^4.1.4", "eslint-plugin-unused-imports": "^4.1.4",
"@next/bundle-analyzer": "^15.0.3", "@next/bundle-analyzer": "^15.0.3",
"@types/node": "^22.9.0", "@types/node": "^22.9.1",
"@types/react": "npm:types-react@19.0.0-rc.1", "@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
@ -63,7 +64,7 @@
"eslint-config-next": "^15.0.3", "eslint-config-next": "^15.0.3",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.8", "prettier-plugin-tailwindcss": "^0.6.9",
"tailwindcss": "^3.4.15", "tailwindcss": "^3.4.15",
"typescript": "^5.6.3" "typescript": "^5.6.3"
}, },