mirror of
https://github.com/hamster1963/nezha-dash.git
synced 2025-04-24 21:10:45 +08:00
Compare commits
No commits in common. "45e5949a69707751b03b34d5d7cb8d1c7c1d87d3" and "10541261da7512220124667a47568a9cc3f40075" have entirely different histories.
45e5949a69
...
10541261da
@ -8,11 +8,6 @@ NEXT_PUBLIC_DisableCartoon=false
|
|||||||
NEXT_PUBLIC_ShowTag=true
|
NEXT_PUBLIC_ShowTag=true
|
||||||
NEXT_PUBLIC_ShowNetTransfer=false
|
NEXT_PUBLIC_ShowNetTransfer=false
|
||||||
NEXT_PUBLIC_ForceUseSvgFlag=false
|
NEXT_PUBLIC_ForceUseSvgFlag=false
|
||||||
NEXT_PUBLIC_FixedTopServerName=false
|
|
||||||
NEXT_PUBLIC_CustomLogo=https://nezha-cf.buycoffee.top/apple-touch-icon.png
|
NEXT_PUBLIC_CustomLogo=https://nezha-cf.buycoffee.top/apple-touch-icon.png
|
||||||
NEXT_PUBLIC_CustomTitle=NezhaDash
|
NEXT_PUBLIC_CustomTitle=NezhaDash
|
||||||
NEXT_PUBLIC_CustomDescription=NezhaDash is a dashboard for Nezha.
|
NEXT_PUBLIC_CustomDescription=NezhaDash is a dashboard for Nezha.
|
||||||
NEXT_PUBLIC_Links="[{"link":"https://github.com/hamster1963/nezha-dash","name":"GitHub"},{"link":"https://buycoffee.top/coffee","name":"Buycoffee☕️"}]"
|
|
||||||
NEXT_PUBLIC_DisableIndex=false
|
|
||||||
NEXT_PUBLIC_BASE_PATH=/
|
|
||||||
NEXT_PUBLIC_ShowTagCount=false
|
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["next/core-web-vitals", "next/typescript"],
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"@next/next/no-img-element": "off",
|
|
||||||
"react-hooks/exhaustive-deps": "off"
|
|
||||||
}
|
|
||||||
}
|
|
1
.github/workflows/Deploy.yml
vendored
1
.github/workflows/Deploy.yml
vendored
@ -51,7 +51,6 @@ jobs:
|
|||||||
|
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build-and-push
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
37
README.md
37
README.md
@ -5,24 +5,29 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### 部署
|
| 一键部署到 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) |
|
||||||
|
| [Vercel-demo](https://nezha-vercel.buycoffee.top) | [Docker-demo](https://nezha-docker.buycoffee.tech) | [Cloudflare-demo](https://nezha-cloudflare.buycoffee.tech) [密码: nezhadash] |
|
||||||
|
|
||||||
支持部署环境:
|
#### 环境变量
|
||||||
|
|
||||||
- Vercel
|
| 变量名 | 含义 | 示例 |
|
||||||
- Cloudflare
|
| ------------------------------ | ------------------------ | ------------------------------------------------------------- |
|
||||||
- Docker
|
| NezhaBaseUrl | nezha 面板地址 | http://120.x.x.x:8008 |
|
||||||
|
| NezhaAuth | nezha 面板 API Token | 5hAY3QX6Nl9B3Uxxxx26KMvOMyXS1Udi |
|
||||||
[演示站点](https://nezha-cf.buycoffee.top)
|
| SitePassword | 页面密码 | **默认**:无密码 |
|
||||||
[说明文档](https://nezhadash-docs.vercel.app)
|
| DefaultLocale | 面板默认显示语言 | **默认**:en [简中:zh 繁中:zh-t 英语:en 日语:ja] |
|
||||||
|
| ForceShowAllServers | 是否强制显示所有服务器 | **默认**:false |
|
||||||
### 如何更新
|
| NEXT_PUBLIC_NezhaFetchInterval | 获取数据间隔(毫秒) | **默认**:2000 |
|
||||||
|
| NEXT_PUBLIC_ShowFlag | 是否显示旗帜 | **默认**:false |
|
||||||
[更新教程](https://buycoffee.top/blog/tech/nezha-upgrade)
|
| NEXT_PUBLIC_DisableCartoon | 是否禁用卡通人物 | **默认**:false |
|
||||||
|
| NEXT_PUBLIC_ShowTag | 是否显示标签 | **默认**:false |
|
||||||
### 环境变量
|
| NEXT_PUBLIC_ShowNetTransfer | 是否显示流量信息 | **默认**:false |
|
||||||
|
| NEXT_PUBLIC_ForceUseSvgFlag | 是否强制使用SVG旗帜 | **默认**:false |
|
||||||
[环境变量介绍](https://nezhadash-docs.vercel.app/environment)
|
| NEXT_PUBLIC_CustomLogo | 自定义Logo | **示例**:https://nezha-cf.buycoffee.top/apple-touch-icon.png |
|
||||||
|
| NEXT_PUBLIC_CustomTitle | 自定义标题 | |
|
||||||
|
| NEXT_PUBLIC_CustomDescription | 自定义描述(无多语言支持) | |
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
@ -1,277 +0,0 @@
|
|||||||
import { GetNezhaData } from "@/lib/serverFetch";
|
|
||||||
import { ServerStackIcon } from "@heroicons/react/20/solid";
|
|
||||||
import DottedMap from "dotted-map";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
export const revalidate = 60
|
|
||||||
|
|
||||||
interface GlobalProps {
|
|
||||||
countries?: string[]; // 国家代码数组,如 ['CN', 'US']
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function ServerGlobal() {
|
|
||||||
const nezhaServerList = await GetNezhaData();
|
|
||||||
|
|
||||||
const countrytList: string[] = [];
|
|
||||||
nezhaServerList.result.forEach((server) => {
|
|
||||||
if (server.host.CountryCode) {
|
|
||||||
server.host.CountryCode = server.host.CountryCode.toUpperCase();
|
|
||||||
if (!countrytList.includes(server.host.CountryCode)) {
|
|
||||||
countrytList.push(server.host.CountryCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return <Global countries={countrytList} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function Global({ countries = [] }: GlobalProps) {
|
|
||||||
const map = new DottedMap({ height: 60, grid: "vertical" });
|
|
||||||
|
|
||||||
// 为每个国家添加点阵
|
|
||||||
countries.forEach((countryCode) => {
|
|
||||||
const coords = getCountryCoordinates(countryCode);
|
|
||||||
if (coords) {
|
|
||||||
map.addPin({
|
|
||||||
lat: coords.lat,
|
|
||||||
lng: coords.lng,
|
|
||||||
svgOptions: { color: "#FF4500", radius: 0.5 },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const finalMap = map.getSVG({
|
|
||||||
radius: 0.35,
|
|
||||||
color: "#D1D5DA",
|
|
||||||
shape: "circle",
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="flex flex-col gap-4 mt-[3.2px]">
|
|
||||||
<Link
|
|
||||||
href={`/`}
|
|
||||||
className="rounded-[50px] w-fit bg-stone-100 p-[10px] transition-all hover:bg-stone-200 dark:hover:bg-stone-700 dark:bg-stone-800"
|
|
||||||
>
|
|
||||||
<ServerStackIcon className="size-4" />
|
|
||||||
</Link>
|
|
||||||
<img
|
|
||||||
src={`data:image/svg+xml;utf8,${encodeURIComponent(finalMap)}`}
|
|
||||||
alt="World Map with Highlighted Countries"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 国家经纬度映射
|
|
||||||
const countryCoordinates: Record<string, { lat: number; lng: number }> = {
|
|
||||||
// 亚洲
|
|
||||||
AF: { lat: 33.0, lng: 65.0 }, // 阿富汗
|
|
||||||
AM: { lat: 40.0, lng: 45.0 }, // 亚美尼亚
|
|
||||||
AZ: { lat: 40.5, lng: 47.5 }, // 阿塞拜疆
|
|
||||||
BD: { lat: 24.0, lng: 90.0 }, // 孟加拉国
|
|
||||||
BH: { lat: 26.0, lng: 50.55 }, // 巴林
|
|
||||||
BT: { lat: 27.5, lng: 90.5 }, // 不丹
|
|
||||||
BN: { lat: 4.5, lng: 114.6667 }, // 文莱
|
|
||||||
KH: { lat: 13.0, lng: 105.0 }, // 柬埔寨
|
|
||||||
CN: { lat: 35.0, lng: 105.0 }, // 中国
|
|
||||||
CY: { lat: 35.0, lng: 33.0 }, // 塞浦路斯
|
|
||||||
GE: { lat: 42.0, lng: 43.5 }, // 格鲁吉亚
|
|
||||||
IN: { lat: 20.0, lng: 77.0 }, // 印度
|
|
||||||
ID: { lat: -5.0, lng: 120.0 }, // 印度尼西亚
|
|
||||||
IR: { lat: 32.0, lng: 53.0 }, // 伊朗
|
|
||||||
IQ: { lat: 33.0, lng: 44.0 }, // 伊拉克
|
|
||||||
IL: { lat: 31.5, lng: 34.75 }, // 以色列
|
|
||||||
JP: { lat: 36.0, lng: 138.0 }, // 日本
|
|
||||||
JO: { lat: 31.0, lng: 36.0 }, // 约旦
|
|
||||||
KZ: { lat: 48.0, lng: 68.0 }, // 哈萨克斯坦
|
|
||||||
KW: { lat: 29.3375, lng: 47.6581 }, // 科威特
|
|
||||||
KG: { lat: 41.0, lng: 75.0 }, // 吉尔吉斯斯坦
|
|
||||||
LA: { lat: 18.0, lng: 105.0 }, // 老挝
|
|
||||||
LB: { lat: 33.8333, lng: 35.8333 }, // 黎巴嫩
|
|
||||||
MY: { lat: 2.5, lng: 112.5 }, // 马来西亚
|
|
||||||
MV: { lat: 3.25, lng: 73.0 }, // 马尔代夫
|
|
||||||
MN: { lat: 46.0, lng: 105.0 }, // 蒙古
|
|
||||||
MM: { lat: 22.0, lng: 98.0 }, // 缅甸
|
|
||||||
NP: { lat: 28.0, lng: 84.0 }, // 尼泊尔
|
|
||||||
OM: { lat: 21.0, lng: 57.0 }, // 阿曼
|
|
||||||
PK: { lat: 30.0, lng: 70.0 }, // 巴基斯坦
|
|
||||||
PH: { lat: 13.0, lng: 122.0 }, // 菲律宾
|
|
||||||
QA: { lat: 25.5, lng: 51.25 }, // 卡塔尔
|
|
||||||
SA: { lat: 25.0, lng: 45.0 }, // 沙特阿拉伯
|
|
||||||
SG: { lat: 1.3667, lng: 103.8 }, // 新加坡
|
|
||||||
KR: { lat: 37.0, lng: 127.5 }, // 韩国
|
|
||||||
LK: { lat: 7.0, lng: 81.0 }, // 斯里兰卡
|
|
||||||
SY: { lat: 35.0, lng: 38.0 }, // 叙利亚
|
|
||||||
TW: { lat: 23.5, lng: 121.0 }, // 台湾
|
|
||||||
TJ: { lat: 39.0, lng: 71.0 }, // 塔吉克斯坦
|
|
||||||
TH: { lat: 15.0, lng: 100.0 }, // 泰国
|
|
||||||
TR: { lat: 39.0, lng: 35.0 }, // 土耳其
|
|
||||||
TM: { lat: 40.0, lng: 60.0 }, // 土库曼斯坦
|
|
||||||
AE: { lat: 24.0, lng: 54.0 }, // 阿联酋
|
|
||||||
UZ: { lat: 41.0, lng: 64.0 }, // 乌兹别克斯坦
|
|
||||||
VN: { lat: 16.0, lng: 106.0 }, // 越南
|
|
||||||
YE: { lat: 15.0, lng: 48.0 }, // 也门
|
|
||||||
PS: { lat: 32.0, lng: 35.25 }, // 巴勒斯坦
|
|
||||||
|
|
||||||
// 欧洲
|
|
||||||
AL: { lat: 41.0, lng: 20.0 }, // 阿尔巴尼亚
|
|
||||||
AD: { lat: 42.5, lng: 1.6 }, // 安道尔
|
|
||||||
AT: { lat: 47.3333, lng: 13.3333 }, // 奥地利
|
|
||||||
BY: { lat: 53.0, lng: 28.0 }, // 白俄罗斯
|
|
||||||
BE: { lat: 50.8333, lng: 4.0 }, // 比利时
|
|
||||||
BA: { lat: 44.0, lng: 18.0 }, // 波黑
|
|
||||||
BG: { lat: 43.0, lng: 25.0 }, // 保加利亚
|
|
||||||
HR: { lat: 45.1667, lng: 15.5 }, // 克罗地亚
|
|
||||||
CZ: { lat: 49.75, lng: 15.5 }, // 捷克
|
|
||||||
DK: { lat: 56.0, lng: 10.0 }, // 丹麦
|
|
||||||
EE: { lat: 59.0, lng: 26.0 }, // 爱沙尼亚
|
|
||||||
FI: { lat: 64.0, lng: 26.0 }, // 芬兰
|
|
||||||
FR: { lat: 46.0, lng: 2.0 }, // 法国
|
|
||||||
DE: { lat: 51.0, lng: 9.0 }, // 德国
|
|
||||||
GR: { lat: 39.0, lng: 22.0 }, // 希腊
|
|
||||||
HU: { lat: 47.0, lng: 20.0 }, // 匈牙利
|
|
||||||
IS: { lat: 65.0, lng: -18.0 }, // 冰岛
|
|
||||||
IE: { lat: 53.0, lng: -8.0 }, // 爱尔兰
|
|
||||||
IT: { lat: 42.8333, lng: 12.8333 }, // 意大利
|
|
||||||
LV: { lat: 57.0, lng: 25.0 }, // 拉脱维亚
|
|
||||||
LI: { lat: 47.1667, lng: 9.5333 }, // 列支敦士登
|
|
||||||
LT: { lat: 56.0, lng: 24.0 }, // 立陶宛
|
|
||||||
LU: { lat: 49.75, lng: 6.1667 }, // 卢森堡
|
|
||||||
MT: { lat: 35.8333, lng: 14.5833 }, // 马耳他
|
|
||||||
MD: { lat: 47.0, lng: 29.0 }, // 摩尔多瓦
|
|
||||||
MC: { lat: 43.7333, lng: 7.4 }, // 摩纳哥
|
|
||||||
ME: { lat: 42.0, lng: 19.0 }, // 黑山
|
|
||||||
NL: { lat: 52.5, lng: 5.75 }, // 荷兰
|
|
||||||
NO: { lat: 62.0, lng: 10.0 }, // 挪威
|
|
||||||
PL: { lat: 52.0, lng: 20.0 }, // 波兰
|
|
||||||
PT: { lat: 39.5, lng: -8.0 }, // 葡萄牙
|
|
||||||
RO: { lat: 46.0, lng: 25.0 }, // 罗马尼亚
|
|
||||||
RU: { lat: 60.0, lng: 100.0 }, // 俄罗斯
|
|
||||||
SM: { lat: 43.7667, lng: 12.4167 }, // 圣马力诺
|
|
||||||
RS: { lat: 44.0, lng: 21.0 }, // 塞尔维亚
|
|
||||||
SK: { lat: 48.6667, lng: 19.5 }, // 斯洛伐克
|
|
||||||
SI: { lat: 46.0, lng: 15.0 }, // 斯洛文尼亚
|
|
||||||
ES: { lat: 40.0, lng: -4.0 }, // 西班牙
|
|
||||||
SE: { lat: 62.0, lng: 15.0 }, // 瑞典
|
|
||||||
CH: { lat: 47.0, lng: 8.0 }, // 瑞士
|
|
||||||
UA: { lat: 49.0, lng: 32.0 }, // 乌克兰
|
|
||||||
GB: { lat: 54.0, lng: -2.0 }, // 英国
|
|
||||||
VA: { lat: 41.9, lng: 12.45 }, // 梵蒂冈
|
|
||||||
|
|
||||||
// 北美洲
|
|
||||||
AG: { lat: 17.05, lng: -61.8 }, // 安提瓜和巴布达
|
|
||||||
BS: { lat: 24.25, lng: -76.0 }, // 巴哈马
|
|
||||||
BB: { lat: 13.1667, lng: -59.5333 }, // 巴巴多斯
|
|
||||||
BZ: { lat: 17.25, lng: -88.75 }, // 伯利兹
|
|
||||||
CA: { lat: 60.0, lng: -95.0 }, // 加拿大
|
|
||||||
CR: { lat: 10.0, lng: -84.0 }, // 哥斯达黎加
|
|
||||||
CU: { lat: 21.5, lng: -80.0 }, // 古巴
|
|
||||||
DM: { lat: 15.4167, lng: -61.3333 }, // 多米尼克
|
|
||||||
DO: { lat: 19.0, lng: -70.6667 }, // 多米尼加共和国
|
|
||||||
SV: { lat: 13.8333, lng: -88.9167 }, // 萨尔瓦多
|
|
||||||
GD: { lat: 12.1167, lng: -61.6667 }, // 格林纳达
|
|
||||||
GT: { lat: 15.5, lng: -90.25 }, // 危地马拉
|
|
||||||
HT: { lat: 19.0, lng: -72.4167 }, // 海地
|
|
||||||
HN: { lat: 15.0, lng: -86.5 }, // 洪都拉斯
|
|
||||||
JM: { lat: 18.25, lng: -77.5 }, // 牙买加
|
|
||||||
MX: { lat: 23.0, lng: -102.0 }, // 墨西哥
|
|
||||||
NI: { lat: 13.0, lng: -85.0 }, // 尼加拉瓜
|
|
||||||
PA: { lat: 9.0, lng: -80.0 }, // 巴拿马
|
|
||||||
KN: { lat: 17.3333, lng: -62.75 }, // 圣基茨和尼维斯
|
|
||||||
LC: { lat: 13.8833, lng: -61.1333 }, // 圣卢西亚
|
|
||||||
VC: { lat: 13.25, lng: -61.2 }, // 圣文森特和格林纳丁斯
|
|
||||||
TT: { lat: 11.0, lng: -61.0 }, // 特立尼达和多巴哥
|
|
||||||
US: { lat: 38.0, lng: -97.0 }, // 美国
|
|
||||||
|
|
||||||
// 南美洲
|
|
||||||
AR: { lat: -34.0, lng: -64.0 }, // 阿根廷
|
|
||||||
BO: { lat: -17.0, lng: -65.0 }, // 玻利维亚
|
|
||||||
BR: { lat: -10.0, lng: -55.0 }, // 巴西
|
|
||||||
CL: { lat: -30.0, lng: -71.0 }, // 智利
|
|
||||||
CO: { lat: 4.0, lng: -72.0 }, // 哥伦比亚
|
|
||||||
EC: { lat: -2.0, lng: -77.5 }, // 厄瓜多尔
|
|
||||||
GY: { lat: 5.0, lng: -59.0 }, // 圭亚那
|
|
||||||
PY: { lat: -23.0, lng: -58.0 }, // 巴拉圭
|
|
||||||
PE: { lat: -10.0, lng: -76.0 }, // 秘鲁
|
|
||||||
SR: { lat: 4.0, lng: -56.0 }, // 苏里南
|
|
||||||
UY: { lat: -33.0, lng: -56.0 }, // 乌拉圭
|
|
||||||
VE: { lat: 8.0, lng: -66.0 }, // 委内瑞拉
|
|
||||||
|
|
||||||
// 大洋洲
|
|
||||||
AU: { lat: -27.0, lng: 133.0 }, // 澳大利亚
|
|
||||||
FJ: { lat: -18.0, lng: 175.0 }, // 斐济
|
|
||||||
KI: { lat: 1.4167, lng: 173.0 }, // 基里巴斯
|
|
||||||
MH: { lat: 9.0, lng: 168.0 }, // 马绍尔群岛
|
|
||||||
FM: { lat: 6.9167, lng: 158.25 }, // 密克罗尼西亚
|
|
||||||
NR: { lat: -0.5333, lng: 166.9167 }, // 瑙鲁
|
|
||||||
NZ: { lat: -41.0, lng: 174.0 }, // 新西兰
|
|
||||||
PW: { lat: 7.5, lng: 134.5 }, // 帕劳
|
|
||||||
PG: { lat: -6.0, lng: 147.0 }, // 巴布亚新几内亚
|
|
||||||
WS: { lat: -13.5833, lng: -172.3333 }, // 萨摩亚
|
|
||||||
SB: { lat: -8.0, lng: 159.0 }, // 所罗门群岛
|
|
||||||
TO: { lat: -20.0, lng: -175.0 }, // 汤加
|
|
||||||
TV: { lat: -8.0, lng: 178.0 }, // 图瓦卢
|
|
||||||
VU: { lat: -16.0, lng: 167.0 }, // 瓦努阿图
|
|
||||||
|
|
||||||
// 非洲
|
|
||||||
DZ: { lat: 28.0, lng: 3.0 }, // 阿尔及利亚
|
|
||||||
AO: { lat: -12.5, lng: 18.5 }, // 安哥拉
|
|
||||||
BJ: { lat: 9.5, lng: 2.25 }, // 贝宁
|
|
||||||
BW: { lat: -22.0, lng: 24.0 }, // 博茨瓦纳
|
|
||||||
BF: { lat: 13.0, lng: -2.0 }, // 布基纳法索
|
|
||||||
BI: { lat: -3.5, lng: 30.0 }, // 布隆迪
|
|
||||||
CM: { lat: 6.0, lng: 12.0 }, // 喀麦隆
|
|
||||||
CV: { lat: 16.0, lng: -24.0 }, // 佛得角
|
|
||||||
CF: { lat: 7.0, lng: 21.0 }, // 中非共和国
|
|
||||||
TD: { lat: 15.0, lng: 19.0 }, // 乍得
|
|
||||||
KM: { lat: -12.1667, lng: 44.25 }, // 科摩罗
|
|
||||||
CG: { lat: -1.0, lng: 15.0 }, // 刚果
|
|
||||||
CD: { lat: 0.0, lng: 25.0 }, // 刚果民主共和国
|
|
||||||
CI: { lat: 8.0, lng: -5.0 }, // 科特迪瓦
|
|
||||||
DJ: { lat: 11.5, lng: 43.0 }, // 吉布提
|
|
||||||
EG: { lat: 27.0, lng: 30.0 }, // 埃及
|
|
||||||
GQ: { lat: 2.0, lng: 10.0 }, // 赤道几内亚
|
|
||||||
ER: { lat: 15.0, lng: 39.0 }, // 厄立特里亚
|
|
||||||
ET: { lat: 8.0, lng: 38.0 }, // 埃塞俄比亚
|
|
||||||
GA: { lat: -1.0, lng: 11.75 }, // 加蓬
|
|
||||||
GM: { lat: 13.4667, lng: -16.5667 }, // 冈比亚
|
|
||||||
GH: { lat: 8.0, lng: -2.0 }, // 加纳
|
|
||||||
GN: { lat: 11.0, lng: -10.0 }, // 几内亚
|
|
||||||
GW: { lat: 12.0, lng: -15.0 }, // 几内亚比绍
|
|
||||||
KE: { lat: 1.0, lng: 38.0 }, // 肯尼亚
|
|
||||||
LS: { lat: -29.5, lng: 28.5 }, // 莱索托
|
|
||||||
LR: { lat: 6.5, lng: -9.5 }, // 利比里亚
|
|
||||||
LY: { lat: 25.0, lng: 17.0 }, // 利比亚
|
|
||||||
MG: { lat: -20.0, lng: 47.0 }, // 马达加斯加
|
|
||||||
MW: { lat: -13.5, lng: 34.0 }, // 马拉维
|
|
||||||
ML: { lat: 17.0, lng: -4.0 }, // 马里
|
|
||||||
MR: { lat: 20.0, lng: -12.0 }, // 毛里塔尼亚
|
|
||||||
MU: { lat: -20.2833, lng: 57.55 }, // 毛里求斯
|
|
||||||
YT: { lat: -12.8333, lng: 45.1667 }, // 马约特
|
|
||||||
MA: { lat: 32.0, lng: -5.0 }, // 摩洛哥
|
|
||||||
MZ: { lat: -18.25, lng: 35.0 }, // 莫桑比克
|
|
||||||
NA: { lat: -22.0, lng: 17.0 }, // 纳米比亚
|
|
||||||
NE: { lat: 16.0, lng: 8.0 }, // 尼日尔
|
|
||||||
NG: { lat: 10.0, lng: 8.0 }, // 尼日利亚
|
|
||||||
RW: { lat: -2.0, lng: 30.0 }, // 卢旺达
|
|
||||||
ST: { lat: 1.0, lng: 7.0 }, // 圣多美和普林西比
|
|
||||||
SN: { lat: 14.0, lng: -14.0 }, // 塞内加尔
|
|
||||||
SC: { lat: -4.5833, lng: 55.6667 }, // 塞舌尔
|
|
||||||
SL: { lat: 8.5, lng: -11.5 }, // 塞拉利昂
|
|
||||||
SO: { lat: 10.0, lng: 49.0 }, // 索马里
|
|
||||||
ZA: { lat: -29.0, lng: 24.0 }, // 南非
|
|
||||||
SD: { lat: 15.0, lng: 30.0 }, // 苏丹
|
|
||||||
SZ: { lat: -26.5, lng: 31.5 }, // 斯威士兰
|
|
||||||
TZ: { lat: -6.0, lng: 35.0 }, // 坦桑尼亚
|
|
||||||
TG: { lat: 8.0, lng: 1.1667 }, // 多哥
|
|
||||||
TN: { lat: 34.0, lng: 9.0 }, // 突尼斯
|
|
||||||
UG: { lat: 1.0, lng: 32.0 }, // 乌干达
|
|
||||||
EH: { lat: 24.5, lng: -13.0 }, // 西撒哈拉
|
|
||||||
ZM: { lat: -15.0, lng: 30.0 }, // 赞比亚
|
|
||||||
ZW: { lat: -20.0, lng: 30.0 }, // 津巴布韦
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据国家代码获取经纬度
|
|
||||||
function getCountryCoordinates(countryCode: string) {
|
|
||||||
return countryCoordinates[countryCode] || null;
|
|
||||||
}
|
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import NetworkChartLoading from "@/app/(main)/ClientComponents/NetworkChartLoading";
|
import NetworkChartLoading from "@/app/(main)/ClientComponents/NetworkChartLoading";
|
||||||
import { NezhaAPIMonitor, ServerMonitorChart } from "@/app/types/nezha-api";
|
import { NezhaAPIMonitor, ServerMonitorChart } from "@/app/types/nezha-api";
|
||||||
|
import { BackIcon } from "@/components/Icon";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@ -20,7 +21,9 @@ import {
|
|||||||
import getEnv from "@/lib/env-entry";
|
import getEnv from "@/lib/env-entry";
|
||||||
import { formatTime, nezhaFetcher } from "@/lib/utils";
|
import { formatTime, nezhaFetcher } from "@/lib/utils";
|
||||||
import { formatRelativeTime } from "@/lib/utils";
|
import { formatRelativeTime } from "@/lib/utils";
|
||||||
|
import { useLocale } from "next-intl";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
|
import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
|
||||||
|
@ -8,7 +8,7 @@ import { Badge } from "@/components/ui/badge";
|
|||||||
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 { cn, formatBytes, nezhaFetcher } from "@/lib/utils";
|
import { cn, formatBytes, nezhaFetcher } from "@/lib/utils";
|
||||||
import { useTranslations } from "next-intl";
|
import { useLocale, useTranslations } from "next-intl";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
@ -29,9 +29,12 @@ export default function ServerDetailClient({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const previousPath = sessionStorage.getItem("fromMainPage");
|
const previousPath = sessionStorage.getItem("lastPath");
|
||||||
if (previousPath) {
|
if (previousPath) {
|
||||||
setHasHistory(true);
|
setHasHistory(true);
|
||||||
|
} else {
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
sessionStorage.setItem("lastPath", currentPath);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -48,6 +51,7 @@ export default function ServerDetailClient({
|
|||||||
nezhaFetcher,
|
nezhaFetcher,
|
||||||
);
|
);
|
||||||
const fallbackData = allFallbackData?.result?.find(
|
const fallbackData = allFallbackData?.result?.find(
|
||||||
|
// @ts-ignore
|
||||||
(item) => item.id === server_id,
|
(item) => item.id === server_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { BackIcon } from "@/components/Icon";
|
import { BackIcon } from "@/components/Icon";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { useLocale } from "next-intl";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
export function ServerDetailChartLoading() {
|
export function ServerDetailChartLoading() {
|
||||||
|
@ -4,20 +4,15 @@ 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 { useStatus } from "@/lib/status-context";
|
|
||||||
import { nezhaFetcher } from "@/lib/utils";
|
import { nezhaFetcher } from "@/lib/utils";
|
||||||
import { GlobeAsiaAustraliaIcon } from "@heroicons/react/20/solid";
|
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
export default function ServerListClient() {
|
export default function ServerListClient() {
|
||||||
const { status, setStatus } = useStatus();
|
|
||||||
const t = useTranslations("ServerListClient");
|
const t = useTranslations("ServerListClient");
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const defaultTag = "defaultTag";
|
const defaultTag = "defaultTag";
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const [tag, setTag] = useState<string>(defaultTag);
|
const [tag, setTag] = useState<string>(defaultTag);
|
||||||
|
|
||||||
@ -78,52 +73,24 @@ export default function ServerListClient() {
|
|||||||
return a.id - b.id;
|
return a.id - b.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const filteredServersByStatus =
|
const allTag = sortedServers.map((server) => server.tag).filter(Boolean);
|
||||||
status === "all"
|
|
||||||
? sortedServers
|
|
||||||
: sortedServers.filter((server) =>
|
|
||||||
[status].includes(server.online_status ? "online" : "offline"),
|
|
||||||
);
|
|
||||||
|
|
||||||
const allTag = filteredServersByStatus
|
|
||||||
.map((server) => server.tag)
|
|
||||||
.filter(Boolean);
|
|
||||||
const uniqueTags = [...new Set(allTag)];
|
const uniqueTags = [...new Set(allTag)];
|
||||||
uniqueTags.unshift(defaultTag);
|
uniqueTags.unshift(defaultTag);
|
||||||
|
|
||||||
const filteredServers =
|
const filteredServers =
|
||||||
tag === defaultTag
|
tag === defaultTag
|
||||||
? filteredServersByStatus
|
? sortedServers
|
||||||
: filteredServersByStatus.filter((server) => server.tag === tag);
|
: sortedServers.filter((server) => server.tag === tag);
|
||||||
|
|
||||||
const tagCountMap: Record<string, number> = {};
|
|
||||||
filteredServersByStatus.forEach((server) => {
|
|
||||||
if (server.tag) {
|
|
||||||
tagCountMap[server.tag] = (tagCountMap[server.tag] || 0) + 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className="flex items-center gap-2 w-full overflow-hidden">
|
{getEnv("NEXT_PUBLIC_ShowTag") === "true" && uniqueTags.length > 1 && (
|
||||||
<button
|
<Switch
|
||||||
onClick={() => {
|
allTag={uniqueTags}
|
||||||
setStatus("all");
|
nowTag={tag}
|
||||||
router.push(`/?global=true`);
|
onTagChange={handleTagChange}
|
||||||
}}
|
/>
|
||||||
className="rounded-[50px] bg-stone-100 p-[10px] transition-all hover:bg-stone-200 dark:hover:bg-stone-700 dark:bg-stone-800"
|
)}
|
||||||
>
|
|
||||||
<GlobeAsiaAustraliaIcon className="size-4" />
|
|
||||||
</button>
|
|
||||||
{getEnv("NEXT_PUBLIC_ShowTag") === "true" && (
|
|
||||||
<Switch
|
|
||||||
allTag={uniqueTags}
|
|
||||||
nowTag={tag}
|
|
||||||
tagCountMap={tagCountMap}
|
|
||||||
onTagChange={handleTagChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
<section
|
<section
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="grid grid-cols-1 gap-2 md:grid-cols-2"
|
className="grid grid-cols-1 gap-2 md:grid-cols-2"
|
||||||
|
@ -4,16 +4,13 @@ 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 { useStatus } from "@/lib/status-context";
|
import { 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";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useSearchParams } from "next/navigation";
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
export default function ServerOverviewClient() {
|
export default function ServerOverviewClient() {
|
||||||
const { status, setStatus } = useStatus();
|
|
||||||
const t = useTranslations("ServerOverviewClient");
|
const t = useTranslations("ServerOverviewClient");
|
||||||
const { data, error, isLoading } = useSWR<ServerApi>(
|
const { data, error, isLoading } = useSWR<ServerApi>(
|
||||||
"/api/server",
|
"/api/server",
|
||||||
@ -21,10 +18,6 @@ export default function ServerOverviewClient() {
|
|||||||
);
|
);
|
||||||
const disableCartoon = getEnv("NEXT_PUBLIC_DisableCartoon") === "true";
|
const disableCartoon = getEnv("NEXT_PUBLIC_DisableCartoon") === "true";
|
||||||
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
|
|
||||||
const global = searchParams.get("global");
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center">
|
<div className="flex flex-col items-center justify-center">
|
||||||
@ -39,10 +32,7 @@ export default function ServerOverviewClient() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<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"))}
|
|
||||||
className="cursor-pointer hover:border-blue-500 transition-all"
|
|
||||||
>
|
|
||||||
<CardContent className="px-6 py-3">
|
<CardContent className="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">
|
||||||
@ -65,15 +55,7 @@ export default function ServerOverviewClient() {
|
|||||||
</section>
|
</section>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card
|
<Card>
|
||||||
onClick={() => (global ? null : setStatus("online"))}
|
|
||||||
className={cn(
|
|
||||||
"cursor-pointer hover:ring-green-500 ring-1 ring-transparent transition-all",
|
|
||||||
{
|
|
||||||
"ring-green-500 ring-2 border-transparent": status === "online",
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CardContent className="px-6 py-3">
|
<CardContent className="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">
|
||||||
@ -97,15 +79,7 @@ export default function ServerOverviewClient() {
|
|||||||
</section>
|
</section>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card
|
<Card>
|
||||||
onClick={() => (global ? null : setStatus("offline"))}
|
|
||||||
className={cn(
|
|
||||||
"cursor-pointer hover:ring-red-500 ring-1 ring-transparent transition-all",
|
|
||||||
{
|
|
||||||
"ring-red-500 ring-2 border-transparent": status === "offline",
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CardContent className="px-6 py-3">
|
<CardContent className="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">
|
||||||
@ -136,11 +110,11 @@ export default function ServerOverviewClient() {
|
|||||||
{t("p_3463-3530_Totalbandwidth")}
|
{t("p_3463-3530_Totalbandwidth")}
|
||||||
</p>
|
</p>
|
||||||
{data?.result ? (
|
{data?.result ? (
|
||||||
<section className="flex flex-col sm:flex-row pt-[8px] sm:items-center items-start gap-1">
|
<section className="flex pt-[4px] items-center gap-2">
|
||||||
<p className="text-[12px] text-nowrap font-semibold">
|
<p className="text-[14px] font-semibold">
|
||||||
↑{formatBytes(data?.total_out_bandwidth)}
|
↑{formatBytes(data?.total_out_bandwidth)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[12px] text-nowrap font-semibold">
|
<p className="text-[14px] font-semibold">
|
||||||
↓{formatBytes(data?.total_in_bandwidth)}
|
↓{formatBytes(data?.total_in_bandwidth)}
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
@ -5,12 +5,12 @@ import ServerDetailChartClient from "@/app/(main)/ClientComponents/ServerDetailC
|
|||||||
import ServerDetailClient from "@/app/(main)/ClientComponents/ServerDetailClient";
|
import ServerDetailClient from "@/app/(main)/ClientComponents/ServerDetailClient";
|
||||||
import TabSwitch from "@/components/TabSwitch";
|
import TabSwitch from "@/components/TabSwitch";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { use, useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export const runtime = "edge";
|
export const runtime = 'edge';
|
||||||
|
|
||||||
|
export default function Page({ params }: { params: { id: string } }) {
|
||||||
|
|
||||||
export default function Page(props: { params: Promise<{ id: string }> }) {
|
|
||||||
const params = use(props.params);
|
|
||||||
const tabs = ["Detail", "Network"];
|
const tabs = ["Detail", "Network"];
|
||||||
const [currentTab, setCurrentTab] = useState(tabs[0]);
|
const [currentTab, setCurrentTab] = useState(tabs[0]);
|
||||||
return (
|
return (
|
||||||
|
@ -51,7 +51,6 @@ function Header() {
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<section className="flex items-center gap-2">
|
<section className="flex items-center gap-2">
|
||||||
<Links />
|
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
<ModeToggle />
|
<ModeToggle />
|
||||||
</section>
|
</section>
|
||||||
@ -61,40 +60,9 @@ function Header() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type links = {
|
|
||||||
link: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function Links() {
|
|
||||||
const linksEnv = getEnv("NEXT_PUBLIC_Links");
|
|
||||||
|
|
||||||
const links: links[] | null = linksEnv ? JSON.parse(linksEnv) : null;
|
|
||||||
|
|
||||||
if (!links) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{links.map((link, index) => {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
key={index}
|
|
||||||
href={link.link}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="flex items-center gap-1 text-sm font-medium opacity-50 transition-opacity hover:opacity-100"
|
|
||||||
>
|
|
||||||
{link.name}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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: () => void, delay: number | null) => {
|
const useInterval = (callback: Function, delay?: number | null) => {
|
||||||
const savedCallback = useRef<() => void>(() => { });
|
const savedCallback = useRef<Function>(() => { });
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
savedCallback.current = callback;
|
savedCallback.current = callback;
|
||||||
});
|
});
|
||||||
|
@ -8,24 +8,17 @@ import React from "react";
|
|||||||
type DashboardProps = {
|
type DashboardProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
export default function MainLayout({ children }: DashboardProps) {
|
export default async function MainLayout({ children }: DashboardProps) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
|
||||||
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-background 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 />
|
||||||
<AuthProtected>{children}</AuthProtected>
|
{!session && getEnv("SitePassword") ? <SignIn /> : children}
|
||||||
<Footer />
|
<Footer />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function AuthProtected({ children }: DashboardProps) {
|
|
||||||
if (getEnv("SitePassword")) {
|
|
||||||
const session = await auth();
|
|
||||||
if (!session) {
|
|
||||||
return <SignIn />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return children;
|
|
||||||
}
|
|
||||||
|
@ -1,45 +1,13 @@
|
|||||||
import ServerList from "@/components/ServerList";
|
import ServerList from "@/components/ServerList";
|
||||||
import ServerOverview from "@/components/ServerOverview";
|
import ServerOverview from "@/components/ServerOverview";
|
||||||
|
|
||||||
export const runtime = "edge";
|
export const runtime = 'edge';
|
||||||
|
|
||||||
import { Loader } from "@/components/loading/Loader";
|
export default function Home() {
|
||||||
import { ServerStackIcon } from "@heroicons/react/20/solid";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { Suspense } from "react";
|
|
||||||
|
|
||||||
import ServerGlobal from "./ClientComponents/Global";
|
|
||||||
|
|
||||||
export default async function Home({
|
|
||||||
searchParams,
|
|
||||||
}: {
|
|
||||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
|
||||||
}) {
|
|
||||||
const global = (await searchParams).global;
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto grid w-full max-w-5xl gap-4 md:gap-6">
|
<div className="mx-auto grid w-full max-w-5xl gap-4 md:gap-6">
|
||||||
<ServerOverview />
|
<ServerOverview />
|
||||||
{!global && <ServerList />}
|
<ServerList />
|
||||||
{global && (
|
|
||||||
<Suspense
|
|
||||||
fallback={
|
|
||||||
<section className="flex flex-col gap-4 mt-[3.2px]">
|
|
||||||
<Link
|
|
||||||
href={`/`}
|
|
||||||
className="rounded-[50px] w-fit bg-stone-100 p-[10px] transition-all hover:bg-stone-200 dark:hover:bg-stone-700 dark:bg-stone-800"
|
|
||||||
>
|
|
||||||
<ServerStackIcon className="size-4" />
|
|
||||||
</Link>
|
|
||||||
<div className="flex min-h-40 flex-col items-center justify-center font-medium text-sm">
|
|
||||||
Loading...
|
|
||||||
<Loader visible={true} />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ServerGlobal />
|
|
||||||
</Suspense>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { handlers } from "@/auth";
|
import { handlers } from "@/auth" // Referring to the auth.ts we just created
|
||||||
|
|
||||||
// Referring to the auth.ts we just created
|
export const runtime = 'edge';
|
||||||
|
|
||||||
export const runtime = "edge";
|
|
||||||
|
|
||||||
export const { GET, POST } = handlers;
|
export const { GET, POST } = handlers;
|
||||||
|
@ -2,23 +2,15 @@ import { auth } from "@/auth";
|
|||||||
import getEnv from "@/lib/env-entry";
|
import getEnv from "@/lib/env-entry";
|
||||||
import { GetServerDetail } from "@/lib/serverFetch";
|
import { GetServerDetail } from "@/lib/serverFetch";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
export const runtime = "edge";
|
export const runtime = 'edge';
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
interface ResError extends Error {
|
export const GET = auth(async function GET(req) {
|
||||||
statusCode: number;
|
if (!req.auth && getEnv("SitePassword")) {
|
||||||
message: string;
|
redirect("/");
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
|
||||||
if (getEnv("SitePassword")) {
|
|
||||||
const session = await auth();
|
|
||||||
if (!session) {
|
|
||||||
redirect("/");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { searchParams } = new URL(req.url);
|
const { searchParams } = new URL(req.url);
|
||||||
@ -43,10 +35,11 @@ export async function GET(req: NextRequest) {
|
|||||||
const detailData = await GetServerDetail({ server_id: serverIdNum });
|
const detailData = await GetServerDetail({ server_id: serverIdNum });
|
||||||
return NextResponse.json(detailData, { status: 200 });
|
return NextResponse.json(detailData, { status: 200 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as ResError;
|
console.error("Error in GET handler:", error);
|
||||||
console.error("Error in GET handler:", err);
|
// @ts-ignore
|
||||||
const statusCode = err.statusCode || 500;
|
const statusCode = error.statusCode || 500;
|
||||||
const message = err.message || "Internal Server Error";
|
// @ts-ignore
|
||||||
|
const message = error.message || "Internal Server Error";
|
||||||
return NextResponse.json({ error: message }, { status: statusCode });
|
return NextResponse.json({ error: message }, { status: statusCode });
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
@ -2,28 +2,19 @@ import { auth } from "@/auth";
|
|||||||
import getEnv from "@/lib/env-entry";
|
import getEnv from "@/lib/env-entry";
|
||||||
import { GetServerMonitor } from "@/lib/serverFetch";
|
import { GetServerMonitor } from "@/lib/serverFetch";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
export const runtime = "edge";
|
export const runtime = "edge";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
interface ResError extends Error {
|
export const GET = auth(async function GET(req) {
|
||||||
statusCode: number;
|
if (!req.auth && getEnv("SitePassword")) {
|
||||||
message: string;
|
redirect("/");
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
|
||||||
if (getEnv("SitePassword")) {
|
|
||||||
const session = await auth();
|
|
||||||
if (!session) {
|
|
||||||
redirect("/");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { searchParams } = new URL(req.url);
|
const { searchParams } = new URL(req.url);
|
||||||
const server_id = searchParams.get("server_id");
|
const server_id = searchParams.get("server_id");
|
||||||
|
|
||||||
if (!server_id) {
|
if (!server_id) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "server_id is required" },
|
{ error: "server_id is required" },
|
||||||
@ -45,10 +36,11 @@ export async function GET(req: NextRequest) {
|
|||||||
});
|
});
|
||||||
return NextResponse.json(monitorData, { status: 200 });
|
return NextResponse.json(monitorData, { status: 200 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as ResError;
|
console.error("Error in GET handler:", error);
|
||||||
console.error("Error in GET handler:", err);
|
// @ts-ignore
|
||||||
const statusCode = err.statusCode || 500;
|
const statusCode = error.statusCode || 500;
|
||||||
const message = err.message || "Internal Server Error";
|
// @ts-ignore
|
||||||
|
const message = error.message || "Internal Server Error";
|
||||||
return NextResponse.json({ error: message }, { status: statusCode });
|
return NextResponse.json({ error: message }, { status: statusCode });
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
@ -8,27 +8,22 @@ export const dynamic = "force-dynamic";
|
|||||||
|
|
||||||
export const runtime = "edge";
|
export const runtime = "edge";
|
||||||
|
|
||||||
interface ResError extends Error {
|
|
||||||
statusCode: number;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET() {
|
|
||||||
if (getEnv("SitePassword")) {
|
export const GET = auth(async function GET(req) {
|
||||||
const session = await auth();
|
if (!req.auth && getEnv("SitePassword")) {
|
||||||
if (!session) {
|
redirect("/");
|
||||||
redirect("/");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await GetNezhaData();
|
const data = await GetNezhaData();
|
||||||
return NextResponse.json(data, { status: 200 });
|
return NextResponse.json(data, { status: 200 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = error as ResError;
|
console.error("Error in GET handler:", error);
|
||||||
console.error("Error in GET handler:", err);
|
// @ts-ignore
|
||||||
const statusCode = err.statusCode || 500;
|
const statusCode = error.statusCode || 500;
|
||||||
const message = err.message || "Internal Server Error";
|
// @ts-ignore
|
||||||
|
const message = error.message || "Internal Server Error";
|
||||||
return NextResponse.json({ error: message }, { status: statusCode });
|
return NextResponse.json({ error: message }, { status: statusCode });
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
// @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 getEnv from "@/lib/env-entry";
|
import getEnv from "@/lib/env-entry";
|
||||||
import { StatusProvider } from "@/lib/status-context";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
@ -20,7 +18,6 @@ const fontSans = FontSans({
|
|||||||
|
|
||||||
const customTitle = getEnv("NEXT_PUBLIC_CustomTitle");
|
const customTitle = getEnv("NEXT_PUBLIC_CustomTitle");
|
||||||
const customDescription = getEnv("NEXT_PUBLIC_CustomDescription");
|
const customDescription = getEnv("NEXT_PUBLIC_CustomDescription");
|
||||||
const disableIndex = getEnv("NEXT_PUBLIC_DisableIndex");
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
manifest: "/manifest.json",
|
manifest: "/manifest.json",
|
||||||
@ -31,10 +28,6 @@ export const metadata: Metadata = {
|
|||||||
title: customTitle || "NezhaDash",
|
title: customTitle || "NezhaDash",
|
||||||
statusBarStyle: "black-translucent",
|
statusBarStyle: "black-translucent",
|
||||||
},
|
},
|
||||||
robots: {
|
|
||||||
index: disableIndex ? false : true,
|
|
||||||
follow: disableIndex ? false : true,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const viewport: Viewport = {
|
export const viewport: Viewport = {
|
||||||
@ -60,10 +53,6 @@ export default async function LocaleLayout({
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.0.0/css/flag-icons.min.css"
|
href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.0.0/css/flag-icons.min.css"
|
||||||
/>
|
/>
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.jsdelivr.net/npm/font-logos@1/assets/font-logos.css"
|
|
||||||
/>
|
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -71,18 +60,16 @@ export default async function LocaleLayout({
|
|||||||
fontSans.variable,
|
fontSans.variable,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MotionProvider>
|
<ThemeProvider
|
||||||
<ThemeProvider
|
attribute="class"
|
||||||
attribute="class"
|
defaultTheme="system"
|
||||||
defaultTheme="system"
|
enableSystem
|
||||||
enableSystem
|
disableTransitionOnChange
|
||||||
disableTransitionOnChange
|
>
|
||||||
>
|
<NextIntlClientProvider messages={messages}>
|
||||||
<NextIntlClientProvider messages={messages}>
|
{children}
|
||||||
<StatusProvider>{children}</StatusProvider>
|
</NextIntlClientProvider>
|
||||||
</NextIntlClientProvider>
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
|
||||||
</MotionProvider>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"types": {
|
|
||||||
"feat": { "title": "🚀 Features" },
|
|
||||||
"fix": { "title": "🔧 Bug Fixes" },
|
|
||||||
"docs": { "title": "📚 Documentation" },
|
|
||||||
"style": { "title": "💄 Styles" },
|
|
||||||
"refactor": { "title": "🔨 Refactor" },
|
|
||||||
"perf": { "title": "🏎 Performance" },
|
|
||||||
"test": { "title": "🚨 Tests" },
|
|
||||||
"build": { "title": "🛠 Build" },
|
|
||||||
"ci": { "title": "👷 CI" },
|
|
||||||
"chore": { "title": "🛗 Chore" },
|
|
||||||
"revert": { "title": "⏪ Revert" }
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,39 +9,29 @@ import {
|
|||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { localeItems } from "@/i18n-metadata";
|
import { localeItems } from "@/i18n-metadata";
|
||||||
import { setUserLocale } from "@/i18n/locale";
|
import { setUserLocale } from "@/i18n/locale";
|
||||||
import { CheckCircleIcon } from "@heroicons/react/20/solid";
|
|
||||||
import { useLocale } from "next-intl";
|
import { useLocale } from "next-intl";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
export function LanguageSwitcher() {
|
export function LanguageSwitcher() {
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
|
|
||||||
const handleSelect = (e: Event, newLocale: string) => {
|
function onChange(value: string) {
|
||||||
e.preventDefault(); // 阻止默认的关闭行为
|
const locale = value;
|
||||||
setUserLocale(newLocale);
|
setUserLocale(locale);
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button variant="outline" size="sm" className="rounded-full px-[9px]">
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="rounded-full px-[9px] bg-white dark:bg-black"
|
|
||||||
>
|
|
||||||
{localeItems.find((item) => item.code === locale)?.name}
|
{localeItems.find((item) => item.code === locale)?.name}
|
||||||
<span className="sr-only">Change language</span>
|
<span className="sr-only">Change language</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="flex flex-col gap-0.5" align="end">
|
<DropdownMenuContent align="end">
|
||||||
{localeItems.map((item) => (
|
{localeItems.map((item) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem key={item.code} onClick={() => onChange(item.code)}>
|
||||||
key={item.code}
|
{item.name}
|
||||||
onSelect={(e) => handleSelect(e, item.code)}
|
|
||||||
className={locale === item.code ? "bg-muted gap-3" : ""}
|
|
||||||
>
|
|
||||||
{item.name}{" "}
|
|
||||||
{locale === item.code && <CheckCircleIcon className="size-4" />}
|
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
@ -3,14 +3,14 @@ import ServerFlag from "@/components/ServerFlag";
|
|||||||
import ServerUsageBar from "@/components/ServerUsageBar";
|
import ServerUsageBar from "@/components/ServerUsageBar";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import getEnv from "@/lib/env-entry";
|
|
||||||
import {
|
import {
|
||||||
GetFontLogoClass,
|
Popover,
|
||||||
GetOsName,
|
PopoverContent,
|
||||||
MageMicrosoftWindows,
|
PopoverTrigger,
|
||||||
} from "@/lib/logo-class";
|
} from "@/components/ui/popover";
|
||||||
|
import getEnv from "@/lib/env-entry";
|
||||||
import { cn, formatBytes, formatNezhaInfo } from "@/lib/utils";
|
import { cn, formatBytes, formatNezhaInfo } from "@/lib/utils";
|
||||||
import { useTranslations } from "next-intl";
|
import { useLocale, useTranslations } from "next-intl";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
@ -21,33 +21,22 @@ export default function ServerCard({
|
|||||||
}) {
|
}) {
|
||||||
const t = useTranslations("ServerCard");
|
const t = useTranslations("ServerCard");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { id, name, country_code, online, cpu, up, down, mem, stg, host } =
|
const { id, name, country_code, online, cpu, up, down, mem, stg, ...props } =
|
||||||
formatNezhaInfo(serverInfo);
|
formatNezhaInfo(serverInfo);
|
||||||
|
|
||||||
const showFlag = getEnv("NEXT_PUBLIC_ShowFlag") === "true";
|
const showFlag = getEnv("NEXT_PUBLIC_ShowFlag") === "true";
|
||||||
const showNetTransfer = getEnv("NEXT_PUBLIC_ShowNetTransfer") === "true";
|
|
||||||
const fixedTopServerName =
|
|
||||||
getEnv("NEXT_PUBLIC_FixedTopServerName") === "true";
|
|
||||||
|
|
||||||
const saveSession = () => {
|
const showNetTransfer = getEnv("NEXT_PUBLIC_ShowNetTransfer") === "true";
|
||||||
sessionStorage.setItem("fromMainPage", "true");
|
|
||||||
};
|
|
||||||
|
|
||||||
return online ? (
|
return online ? (
|
||||||
<Link onClick={saveSession} href={`/${id}`} prefetch={true}>
|
<Link href={`/${id}`} prefetch={true}>
|
||||||
<Card
|
<Card
|
||||||
className={cn(
|
className={
|
||||||
"flex flex-col items-center justify-start gap-3 p-3 md:px-5 cursor-pointer",
|
"flex flex-col items-center justify-start gap-3 p-3 md:px-5 lg:flex-row cursor-pointer"
|
||||||
{
|
}
|
||||||
"flex-col": fixedTopServerName,
|
|
||||||
"lg:flex-row": !fixedTopServerName,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<section
|
<section
|
||||||
className={cn("grid items-center gap-2", {
|
className="grid items-center gap-2 lg:w-28 "
|
||||||
"lg:w-40": !fixedTopServerName,
|
|
||||||
})}
|
|
||||||
style={{ gridTemplateColumns: "auto auto 1fr" }}
|
style={{ gridTemplateColumns: "auto auto 1fr" }}
|
||||||
>
|
>
|
||||||
<span className="h-2 w-2 shrink-0 rounded-full bg-green-500 self-center"></span>
|
<span className="h-2 w-2 shrink-0 rounded-full bg-green-500 self-center"></span>
|
||||||
@ -59,46 +48,17 @@ export default function ServerCard({
|
|||||||
>
|
>
|
||||||
{showFlag ? <ServerFlag country_code={country_code} /> : null}
|
{showFlag ? <ServerFlag country_code={country_code} /> : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<p
|
||||||
<p
|
className={cn(
|
||||||
className={cn(
|
"break-all font-bold tracking-tight",
|
||||||
"break-all font-bold tracking-tight",
|
showFlag ? "text-xs" : "text-sm",
|
||||||
showFlag ? "text-xs" : "text-sm",
|
)}
|
||||||
)}
|
>
|
||||||
>
|
{name}
|
||||||
{name}
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<section
|
<section className={"grid grid-cols-5 items-center gap-3"}>
|
||||||
className={cn("grid grid-cols-5 items-center gap-3", {
|
|
||||||
"lg:grid-cols-6 lg:gap-4": fixedTopServerName,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{fixedTopServerName && (
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
"hidden col-span-1 items-center lg:flex lg:flex-row gap-2"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="text-xs font-semibold">
|
|
||||||
{host.Platform.includes("Windows") ? (
|
|
||||||
<MageMicrosoftWindows className="size-[10px]" />
|
|
||||||
) : (
|
|
||||||
<p className={`fl-${GetFontLogoClass(host.Platform)}`} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className={"flex w-14 flex-col"}>
|
|
||||||
<p className="text-xs text-muted-foreground">{t("System")}</p>
|
|
||||||
<div className="flex items-center text-[10.5px] font-semibold">
|
|
||||||
{host.Platform.includes("Windows")
|
|
||||||
? "Windows"
|
|
||||||
: GetOsName(host.Platform)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={"flex w-14 flex-col"}>
|
<div className={"flex w-14 flex-col"}>
|
||||||
<p className="text-xs text-muted-foreground">{t("CPU")}</p>
|
<p className="text-xs text-muted-foreground">{t("CPU")}</p>
|
||||||
<div className="flex items-center text-xs font-semibold">
|
<div className="flex items-center text-xs font-semibold">
|
||||||
@ -123,17 +83,13 @@ export default function ServerCard({
|
|||||||
<div className={"flex w-14 flex-col"}>
|
<div className={"flex w-14 flex-col"}>
|
||||||
<p className="text-xs text-muted-foreground">{t("Upload")}</p>
|
<p className="text-xs text-muted-foreground">{t("Upload")}</p>
|
||||||
<div className="flex items-center text-xs font-semibold">
|
<div className="flex items-center text-xs font-semibold">
|
||||||
{up >= 1024
|
{up.toFixed(2)}M/s
|
||||||
? `${(up / 1024).toFixed(2)}G/s`
|
|
||||||
: `${up.toFixed(2)}M/s`}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={"flex w-14 flex-col"}>
|
<div className={"flex w-14 flex-col"}>
|
||||||
<p className="text-xs text-muted-foreground">{t("Download")}</p>
|
<p className="text-xs text-muted-foreground">{t("Download")}</p>
|
||||||
<div className="flex items-center text-xs font-semibold">
|
<div className="flex items-center text-xs font-semibold">
|
||||||
{down >= 1024
|
{down.toFixed(2)}M/s
|
||||||
? `${(down / 1024).toFixed(2)}G/s`
|
|
||||||
: `${down.toFixed(2)}M/s`}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -164,42 +120,41 @@ export default function ServerCard({
|
|||||||
) : (
|
) : (
|
||||||
<Card
|
<Card
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col items-center justify-start gap-3 p-3 md:px-5",
|
"flex flex-col items-center justify-start gap-3 p-3 md:px-5 lg:flex-row",
|
||||||
showNetTransfer
|
showNetTransfer
|
||||||
? "lg:min-h-[91px] min-h-[123px]"
|
? "lg:min-h-[91px] min-h-[123px]"
|
||||||
: "lg:min-h-[61px] min-h-[93px]",
|
: "lg:min-h-[61px] min-h-[93px]",
|
||||||
{
|
|
||||||
"flex-col": fixedTopServerName,
|
|
||||||
"lg:flex-row": !fixedTopServerName,
|
|
||||||
},
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<section
|
<Popover>
|
||||||
className={cn("grid items-center gap-2", {
|
<PopoverTrigger asChild>
|
||||||
"lg:w-40": !fixedTopServerName,
|
<section
|
||||||
})}
|
className="grid items-center gap-2 lg:w-28"
|
||||||
style={{ gridTemplateColumns: "auto auto 1fr" }}
|
style={{ gridTemplateColumns: "auto auto 1fr" }}
|
||||||
>
|
|
||||||
<span className="h-2 w-2 shrink-0 rounded-full bg-red-500 self-center"></span>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex items-center justify-center",
|
|
||||||
showFlag ? "min-w-[17px]" : "min-w-0",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{showFlag ? <ServerFlag country_code={country_code} /> : null}
|
|
||||||
</div>
|
|
||||||
<div className="relative">
|
|
||||||
<p
|
|
||||||
className={cn(
|
|
||||||
"break-all font-bold tracking-tight",
|
|
||||||
showFlag ? "text-xs max-w-[80px]" : "text-sm",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{name}
|
<span className="h-2 w-2 shrink-0 rounded-full bg-red-500 self-center"></span>
|
||||||
</p>
|
<div
|
||||||
</div>
|
className={cn(
|
||||||
</section>
|
"flex items-center justify-center",
|
||||||
|
showFlag ? "min-w-[17px]" : "min-w-0",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{showFlag ? <ServerFlag country_code={country_code} /> : null}
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={cn(
|
||||||
|
"break-all font-bold tracking-tight",
|
||||||
|
showFlag ? "text-xs" : "text-sm",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-fit p-2" side="top">
|
||||||
|
<p className="text-sm text-muted-foreground">{t("Offline")}</p>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import ServerListClient from "@/app/(main)/ClientComponents/ServerListClient";
|
import ServerListClient from "@/app/(main)/ClientComponents/ServerListClient";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export default async function ServerList() {
|
export default async function ServerList() {
|
||||||
return <ServerListClient />;
|
return <ServerListClient />;
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import getEnv from "@/lib/env-entry";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { m } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import React, { createRef, useEffect, useRef } from "react";
|
import React, { createRef, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
export default function Switch({
|
export default function Switch({
|
||||||
allTag,
|
allTag,
|
||||||
nowTag,
|
nowTag,
|
||||||
tagCountMap,
|
|
||||||
onTagChange,
|
onTagChange,
|
||||||
}: {
|
}: {
|
||||||
allTag: string[];
|
allTag: string[];
|
||||||
nowTag: string;
|
nowTag: string;
|
||||||
tagCountMap: Record<string, number>;
|
|
||||||
onTagChange: (tag: string) => void;
|
onTagChange: (tag: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
@ -26,7 +23,7 @@ export default function Switch({
|
|||||||
if (savedTag && allTag.includes(savedTag)) {
|
if (savedTag && allTag.includes(savedTag)) {
|
||||||
onTagChange(savedTag);
|
onTagChange(savedTag);
|
||||||
}
|
}
|
||||||
}, [allTag, onTagChange]);
|
}, [allTag]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = scrollRef.current;
|
const container = scrollRef.current;
|
||||||
@ -77,7 +74,7 @@ export default function Switch({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{nowTag === tag && (
|
{nowTag === tag && (
|
||||||
<m.div
|
<motion.div
|
||||||
layoutId="nav-item"
|
layoutId="nav-item"
|
||||||
className="absolute inset-0 z-10 h-full w-full content-center bg-white shadow-lg shadow-black/5 dark:bg-stone-700 dark:shadow-white/5"
|
className="absolute inset-0 z-10 h-full w-full content-center bg-white shadow-lg shadow-black/5 dark:bg-stone-700 dark:shadow-white/5"
|
||||||
style={{
|
style={{
|
||||||
@ -87,15 +84,9 @@ export default function Switch({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="relative z-20 flex items-center gap-1">
|
<div className="relative z-20 flex items-center gap-1">
|
||||||
<div className="whitespace-nowrap flex items-center gap-2">
|
<p className="whitespace-nowrap">
|
||||||
{tag === "defaultTag" ? t("defaultTag") : tag}{" "}
|
{tag === "defaultTag" ? t("defaultTag") : tag}
|
||||||
{getEnv("NEXT_PUBLIC_ShowTagCount") === "true" &&
|
</p>
|
||||||
tag !== "defaultTag" && (
|
|
||||||
<div className="w-fit px-1.5 rounded-full bg-muted">
|
|
||||||
{tagCountMap[tag]}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { m } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ export default function TabSwitch({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{currentTab === tab && (
|
{currentTab === tab && (
|
||||||
<m.div
|
<motion.div
|
||||||
layoutId="tab-switch"
|
layoutId="tab-switch"
|
||||||
className="absolute inset-0 z-10 h-full w-full content-center bg-white shadow-lg shadow-black/5 dark:bg-stone-700 dark:shadow-white/5"
|
className="absolute inset-0 z-10 h-full w-full content-center bg-white shadow-lg shadow-black/5 dark:bg-stone-700 dark:shadow-white/5"
|
||||||
style={{
|
style={{
|
||||||
|
@ -7,55 +7,32 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { CheckCircleIcon } from "@heroicons/react/20/solid";
|
|
||||||
import { Moon, Sun } from "lucide-react";
|
import { Moon, Sun } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
export function ModeToggle() {
|
export function ModeToggle() {
|
||||||
const { setTheme, theme } = useTheme();
|
const { setTheme } = useTheme();
|
||||||
const t = useTranslations("ThemeSwitcher");
|
const t = useTranslations("ThemeSwitcher");
|
||||||
|
|
||||||
const handleSelect = (e: Event, newTheme: string) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setTheme(newTheme);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button variant="outline" size="sm" className="rounded-full px-[9px]">
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="rounded-full px-[9px] bg-white dark:bg-black"
|
|
||||||
>
|
|
||||||
<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||||
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||||
<span className="sr-only">Toggle theme</span>
|
<span className="sr-only">Toggle theme</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="flex flex-col gap-0.5" align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem onClick={() => setTheme("light")}>
|
||||||
className={cn({ "gap-3 bg-muted": theme === "light" })}
|
{t("Light")}
|
||||||
onSelect={(e) => handleSelect(e, "light")}
|
|
||||||
>
|
|
||||||
{t("Light")}{" "}
|
|
||||||
{theme === "light" && <CheckCircleIcon className="size-4" />}
|
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
||||||
className={cn({ "gap-3 bg-muted": theme === "dark" })}
|
{t("Dark")}
|
||||||
onSelect={(e) => handleSelect(e, "dark")}
|
|
||||||
>
|
|
||||||
{t("Dark")}{" "}
|
|
||||||
{theme === "dark" && <CheckCircleIcon className="size-4" />}
|
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem onClick={() => setTheme("system")}>
|
||||||
className={cn({ "gap-3 bg-muted": theme === "system" })}
|
{t("System")}
|
||||||
onSelect={(e) => handleSelect(e, "system")}
|
|
||||||
>
|
|
||||||
{t("System")}{" "}
|
|
||||||
{theme === "system" && <CheckCircleIcon className="size-4" />}
|
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export { domMax as default } from "framer-motion";
|
|
@ -1,14 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { LazyMotion } from "framer-motion";
|
|
||||||
|
|
||||||
const loadFeatures = () =>
|
|
||||||
import("./framer-lazy-feature").then((res) => res.default);
|
|
||||||
|
|
||||||
export const MotionProvider = ({ children }: { children: React.ReactNode }) => {
|
|
||||||
return (
|
|
||||||
<LazyMotion features={loadFeatures} strict key="framer">
|
|
||||||
{children}
|
|
||||||
</LazyMotion>
|
|
||||||
);
|
|
||||||
};
|
|
@ -8,7 +8,7 @@ import * as RechartsPrimitive from "recharts";
|
|||||||
const THEMES = { light: "", dark: ".dark" } as const;
|
const THEMES = { light: "", dark: ".dark" } as const;
|
||||||
|
|
||||||
export type ChartConfig = {
|
export type ChartConfig = {
|
||||||
[k: string]: {
|
[k in string]: {
|
||||||
label?: React.ReactNode;
|
label?: React.ReactNode;
|
||||||
icon?: React.ComponentType;
|
icon?: React.ComponentType;
|
||||||
} & (
|
} & (
|
||||||
@ -68,7 +68,7 @@ ChartContainer.displayName = "Chart";
|
|||||||
|
|
||||||
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||||
const colorConfig = Object.entries(config).filter(
|
const colorConfig = Object.entries(config).filter(
|
||||||
([, config]) => config.theme || config.color,
|
([_, config]) => config.theme || config.color,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!colorConfig.length) {
|
if (!colorConfig.length) {
|
||||||
|
@ -64,7 +64,7 @@ const DropdownMenuContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 overflow-hidden rounded-md border bg-popover p-1.5 text-popover-foreground shadow-2xl dark:shadow-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"z-50 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg shadow-neutral-200/50 dark:shadow-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -82,7 +82,7 @@ const DropdownMenuItem = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.Item
|
<DropdownMenuPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-[10px] px-2 py-1.5 text-xs font-medium outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-xs font-medium outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
inset && "pl-8",
|
inset && "pl-8",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
export interface InputProps
|
||||||
|
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ className, type, ...props }, ref) => {
|
({ className, type, ...props }, ref) => {
|
||||||
|
@ -18,7 +18,7 @@ const TooltipContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 overflow-hidden rounded-[10px] border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -8,10 +8,6 @@ NEXT_PUBLIC_DisableCartoon=false
|
|||||||
NEXT_PUBLIC_ShowTag=true
|
NEXT_PUBLIC_ShowTag=true
|
||||||
NEXT_PUBLIC_ShowNetTransfer=false
|
NEXT_PUBLIC_ShowNetTransfer=false
|
||||||
NEXT_PUBLIC_ForceUseSvgFlag=false
|
NEXT_PUBLIC_ForceUseSvgFlag=false
|
||||||
NEXT_PUBLIC_FixedTopServerName=false
|
|
||||||
NEXT_PUBLIC_CustomLogo=https://nezha-cf.buycoffee.top/apple-touch-icon.png
|
NEXT_PUBLIC_CustomLogo=https://nezha-cf.buycoffee.top/apple-touch-icon.png
|
||||||
NEXT_PUBLIC_CustomTitle=NezhaDash
|
NEXT_PUBLIC_CustomTitle=NezhaDash
|
||||||
NEXT_PUBLIC_CustomDescription=NezhaDash is a dashboard for Nezha.
|
NEXT_PUBLIC_CustomDescription=NezhaDash is a dashboard for Nezha.
|
||||||
NEXT_PUBLIC_Links="[{"link":"https://github.com/hamster1963/nezha-dash","name":"GitHub"},{"link":"https://buycoffee.top/coffee","name":"Buycoffee☕️"}]"
|
|
||||||
NEXT_PUBLIC_DisableIndex=false
|
|
||||||
NEXT_PUBLIC_ShowTagCount=false
|
|
83
git-cliff-config/cliff.toml
Normal file
83
git-cliff-config/cliff.toml
Normal 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"
|
@ -6,12 +6,9 @@ import { cookies } from "next/headers";
|
|||||||
const COOKIE_NAME = "NEXT_LOCALE";
|
const COOKIE_NAME = "NEXT_LOCALE";
|
||||||
|
|
||||||
export async function getUserLocale() {
|
export async function getUserLocale() {
|
||||||
return (
|
return cookies().get(COOKIE_NAME)?.value || (getEnv("DefaultLocale") ?? "en");
|
||||||
(await cookies()).get(COOKIE_NAME)?.value ||
|
|
||||||
(getEnv("DefaultLocale") ?? "en")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setUserLocale(locale: string) {
|
export async function setUserLocale(locale: string) {
|
||||||
(await cookies()).set(COOKIE_NAME, locale);
|
cookies().set(COOKIE_NAME, locale);
|
||||||
}
|
}
|
||||||
|
@ -1,149 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import type { SVGProps } from "react";
|
|
||||||
|
|
||||||
export function GetFontLogoClass(platform: string): string {
|
|
||||||
if (
|
|
||||||
[
|
|
||||||
"almalinux",
|
|
||||||
"alpine",
|
|
||||||
"aosc",
|
|
||||||
"apple",
|
|
||||||
"archlinux",
|
|
||||||
"archlabs",
|
|
||||||
"artix",
|
|
||||||
"budgie",
|
|
||||||
"centos",
|
|
||||||
"coreos",
|
|
||||||
"debian",
|
|
||||||
"deepin",
|
|
||||||
"devuan",
|
|
||||||
"docker",
|
|
||||||
"elementary",
|
|
||||||
"fedora",
|
|
||||||
"ferris",
|
|
||||||
"flathub",
|
|
||||||
"freebsd",
|
|
||||||
"gentoo",
|
|
||||||
"gnu-guix",
|
|
||||||
"illumos",
|
|
||||||
"kali-linux",
|
|
||||||
"linuxmint",
|
|
||||||
"mageia",
|
|
||||||
"mandriva",
|
|
||||||
"manjaro",
|
|
||||||
"nixos",
|
|
||||||
"openbsd",
|
|
||||||
"opensuse",
|
|
||||||
"pop-os",
|
|
||||||
"raspberry-pi",
|
|
||||||
"redhat",
|
|
||||||
"rocky-linux",
|
|
||||||
"sabayon",
|
|
||||||
"slackware",
|
|
||||||
"snappy",
|
|
||||||
"solus",
|
|
||||||
"tux",
|
|
||||||
"ubuntu",
|
|
||||||
"void",
|
|
||||||
"zorin",
|
|
||||||
].indexOf(platform) > -1
|
|
||||||
) {
|
|
||||||
return platform;
|
|
||||||
}
|
|
||||||
if (platform == "darwin") {
|
|
||||||
return "apple";
|
|
||||||
}
|
|
||||||
if (["openwrt", "linux", "immortalwrt"].indexOf(platform) > -1) {
|
|
||||||
return "tux";
|
|
||||||
}
|
|
||||||
if (platform == "amazon") {
|
|
||||||
return "redhat";
|
|
||||||
}
|
|
||||||
if (platform == "arch") {
|
|
||||||
return "archlinux";
|
|
||||||
}
|
|
||||||
if (platform.toLowerCase().includes("opensuse")) {
|
|
||||||
return "opensuse";
|
|
||||||
}
|
|
||||||
return "tux";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GetOsName(platform: string): string {
|
|
||||||
if (
|
|
||||||
[
|
|
||||||
"almalinux",
|
|
||||||
"alpine",
|
|
||||||
"aosc",
|
|
||||||
"apple",
|
|
||||||
"archlinux",
|
|
||||||
"archlabs",
|
|
||||||
"artix",
|
|
||||||
"budgie",
|
|
||||||
"centos",
|
|
||||||
"coreos",
|
|
||||||
"debian",
|
|
||||||
"deepin",
|
|
||||||
"devuan",
|
|
||||||
"docker",
|
|
||||||
"fedora",
|
|
||||||
"ferris",
|
|
||||||
"flathub",
|
|
||||||
"freebsd",
|
|
||||||
"gentoo",
|
|
||||||
"gnu-guix",
|
|
||||||
"illumos",
|
|
||||||
"linuxmint",
|
|
||||||
"mageia",
|
|
||||||
"mandriva",
|
|
||||||
"manjaro",
|
|
||||||
"nixos",
|
|
||||||
"openbsd",
|
|
||||||
"opensuse",
|
|
||||||
"pop-os",
|
|
||||||
"redhat",
|
|
||||||
"sabayon",
|
|
||||||
"slackware",
|
|
||||||
"snappy",
|
|
||||||
"solus",
|
|
||||||
"tux",
|
|
||||||
"ubuntu",
|
|
||||||
"void",
|
|
||||||
"zorin",
|
|
||||||
].indexOf(platform) > -1
|
|
||||||
) {
|
|
||||||
return platform.charAt(0).toUpperCase() + platform.slice(1);
|
|
||||||
}
|
|
||||||
if (platform == "darwin") {
|
|
||||||
return "macOS";
|
|
||||||
}
|
|
||||||
if (["openwrt", "linux", "immortalwrt"].indexOf(platform) > -1) {
|
|
||||||
return "Linux";
|
|
||||||
}
|
|
||||||
if (platform == "amazon") {
|
|
||||||
return "Redhat";
|
|
||||||
}
|
|
||||||
if (platform == "arch") {
|
|
||||||
return "Archlinux";
|
|
||||||
}
|
|
||||||
if (platform.toLowerCase().includes("opensuse")) {
|
|
||||||
return "Opensuse";
|
|
||||||
}
|
|
||||||
return "Linux";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function MageMicrosoftWindows(props: SVGProps<SVGSVGElement>) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="1em"
|
|
||||||
height="1em"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M2.75 7.189V2.865c0-.102 0-.115.115-.115h8.622c.128 0 .14 0 .14.128V11.5c0 .128 0 .128-.14.128H2.865c-.102 0-.115 0-.115-.116zM7.189 21.25H2.865c-.102 0-.115 0-.115-.116V12.59c0-.128 0-.128.128-.128h8.635c.102 0 .115 0 .115.115v8.57c0 .09 0 .103-.116.103zM21.25 7.189v4.31c0 .116 0 .116-.116.116h-8.557c-.102 0-.128 0-.128-.115V2.865c0-.09 0-.102.115-.102h8.48c.206 0 .206 0 .206.205zm-8.763 9.661v-4.273c0-.09 0-.115.103-.09h8.621c.026 0 0 .09 0 .142v8.518a.06.06 0 0 1-.017.06a.06.06 0 0 1-.06.017H12.54s-.09 0-.077-.09V16.85z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
44
lib/sseFetch.tsx
Normal file
44
lib/sseFetch.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import useSWRSubscription, {
|
||||||
|
type SWRSubscriptionOptions,
|
||||||
|
} from "swr/subscription";
|
||||||
|
|
||||||
|
type LooseObject = {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SSEDataFetch(url: string, fallbackData?: LooseObject): any {
|
||||||
|
const { data } = useSWRSubscription<LooseObject>(
|
||||||
|
url,
|
||||||
|
(key: string | URL, { next }: SWRSubscriptionOptions<LooseObject>) => {
|
||||||
|
const source = new EventSource(key);
|
||||||
|
source.onmessage = (event) => {
|
||||||
|
const parsedData = JSON.parse(event.data);
|
||||||
|
next(null, parsedData);
|
||||||
|
};
|
||||||
|
source.onerror = () => next(new Error("EventSource error"));
|
||||||
|
return () => source.close();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fallbackData: fallbackData,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verifySSEConnection(url: string): Promise<{ name: string }> {
|
||||||
|
return new Promise<{ name: string }>((resolve, reject) => {
|
||||||
|
const eventSource = new EventSource(url);
|
||||||
|
|
||||||
|
eventSource.onopen = () => {
|
||||||
|
resolve({ name: "SSE Connected" });
|
||||||
|
eventSource.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSource.onerror = () => {
|
||||||
|
reject("Failed to connect");
|
||||||
|
eventSource.close();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { ReactNode, createContext, useContext, useState } from "react";
|
|
||||||
|
|
||||||
type Status = "all" | "online" | "offline";
|
|
||||||
|
|
||||||
interface StatusContextType {
|
|
||||||
status: Status;
|
|
||||||
setStatus: (status: Status) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StatusContext = createContext<StatusContextType | undefined>(undefined);
|
|
||||||
|
|
||||||
export function StatusProvider({ children }: { children: ReactNode }) {
|
|
||||||
const [status, setStatus] = useState<Status>("all");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StatusContext.Provider value={{ status, setStatus }}>
|
|
||||||
{children}
|
|
||||||
</StatusContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useStatus() {
|
|
||||||
const context = useContext(StatusContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error("useStatus must be used within a StatusProvider");
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
22
lib/utils.ts
22
lib/utils.ts
@ -10,16 +10,16 @@ export function formatNezhaInfo(serverInfo: NezhaAPISafe) {
|
|||||||
return {
|
return {
|
||||||
...serverInfo,
|
...serverInfo,
|
||||||
cpu: serverInfo.status.CPU,
|
cpu: serverInfo.status.CPU,
|
||||||
process: serverInfo.status.ProcessCount || 0,
|
process: serverInfo.status.ProcessCount,
|
||||||
up: serverInfo.status.NetOutSpeed / 1024 / 1024 || 0,
|
up: serverInfo.status.NetOutSpeed / 1024 / 1024,
|
||||||
down: serverInfo.status.NetInSpeed / 1024 / 1024 || 0,
|
down: serverInfo.status.NetInSpeed / 1024 / 1024,
|
||||||
online: serverInfo.online_status,
|
online: serverInfo.online_status,
|
||||||
tcp: serverInfo.status.TcpConnCount || 0,
|
tcp: serverInfo.status.TcpConnCount,
|
||||||
udp: serverInfo.status.UdpConnCount || 0,
|
udp: serverInfo.status.UdpConnCount,
|
||||||
mem: (serverInfo.status.MemUsed / serverInfo.host.MemTotal) * 100 || 0,
|
mem: (serverInfo.status.MemUsed / serverInfo.host.MemTotal) * 100,
|
||||||
swap: (serverInfo.status.SwapUsed / serverInfo.host.SwapTotal) * 100 || 0,
|
swap: (serverInfo.status.SwapUsed / serverInfo.host.SwapTotal) * 100,
|
||||||
disk: (serverInfo.status.DiskUsed / serverInfo.host.DiskTotal) * 100 || 0,
|
disk: (serverInfo.status.DiskUsed / serverInfo.host.DiskTotal) * 100,
|
||||||
stg: (serverInfo.status.DiskUsed / serverInfo.host.DiskTotal) * 100 || 0,
|
stg: (serverInfo.status.DiskUsed / serverInfo.host.DiskTotal) * 100,
|
||||||
country_code: serverInfo.host.CountryCode,
|
country_code: serverInfo.host.CountryCode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -76,9 +76,9 @@ export const nezhaFetcher = async (url: string) => {
|
|||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const error = new Error("An error occurred while fetching the data.");
|
const error = new Error("An error occurred while fetching the data.");
|
||||||
// @ts-expect-error - res.json() returns a Promise<any>
|
// @ts-ignore
|
||||||
error.info = await res.json();
|
error.info = await res.json();
|
||||||
// @ts-expect-error - res.status is a number
|
// @ts-ignore
|
||||||
error.status = res.status;
|
error.status = res.status;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"defaultTag": "All"
|
"defaultTag": "All"
|
||||||
},
|
},
|
||||||
"ServerCard": {
|
"ServerCard": {
|
||||||
"System": "System",
|
|
||||||
"CPU": "CPU",
|
"CPU": "CPU",
|
||||||
"Mem": "Mem",
|
"Mem": "Mem",
|
||||||
"STG": "STG",
|
"STG": "STG",
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"defaultTag": "すべて"
|
"defaultTag": "すべて"
|
||||||
},
|
},
|
||||||
"ServerCard": {
|
"ServerCard": {
|
||||||
"System": "システム",
|
|
||||||
"CPU": "CPU",
|
"CPU": "CPU",
|
||||||
"Mem": "Mem",
|
"Mem": "Mem",
|
||||||
"STG": "STG",
|
"STG": "STG",
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"defaultTag": "全部"
|
"defaultTag": "全部"
|
||||||
},
|
},
|
||||||
"ServerCard": {
|
"ServerCard": {
|
||||||
"System": "系統",
|
|
||||||
"CPU": "CPU",
|
"CPU": "CPU",
|
||||||
"Mem": "記憶體",
|
"Mem": "記憶體",
|
||||||
"STG": "儲存",
|
"STG": "儲存",
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"defaultTag": "全部"
|
"defaultTag": "全部"
|
||||||
},
|
},
|
||||||
"ServerCard": {
|
"ServerCard": {
|
||||||
"System": "系统",
|
|
||||||
"CPU": "CPU",
|
"CPU": "CPU",
|
||||||
"Mem": "内存",
|
"Mem": "内存",
|
||||||
"STG": "存储",
|
"STG": "存储",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import withPWAInit from "@ducanh2912/next-pwa";
|
import withPWAInit from "@ducanh2912/next-pwa";
|
||||||
import withBundleAnalyzer from "@next/bundle-analyzer";
|
import withBundleAnalyzer from "@next/bundle-analyzer";
|
||||||
import createNextIntlPlugin from "next-intl/plugin";
|
import createNextIntlPlugin from "next-intl/plugin";
|
||||||
import { env } from "next-runtime-env";
|
|
||||||
|
|
||||||
const bundleAnalyzer = withBundleAnalyzer({
|
const bundleAnalyzer = withBundleAnalyzer({
|
||||||
enabled: process.env.ANALYZE === "true",
|
enabled: process.env.ANALYZE === "true",
|
||||||
@ -23,7 +22,7 @@ const withPWA = withPWAInit({
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
basePath: env("NEXT_PUBLIC_BASE_PATH") || "",
|
reactStrictMode: true,
|
||||||
logging: {
|
logging: {
|
||||||
fetches: {
|
fetches: {
|
||||||
fullUrl: true,
|
fullUrl: true,
|
||||||
|
57
package.json
57
package.json
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "nezha-dash",
|
"name": "nezha-dash",
|
||||||
"version": "1.4.0-fix",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3040",
|
"dev": "next dev -p 3020",
|
||||||
"start": "node .next/standalone/server.js",
|
"start": "node .next/standalone/server.js",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/",
|
"build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/",
|
||||||
@ -12,7 +12,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ducanh2912/next-pwa": "^10.2.9",
|
"@ducanh2912/next-pwa": "^10.2.9",
|
||||||
"@heroicons/react": "^2.2.0",
|
|
||||||
"@radix-ui/react-dialog": "^1.1.2",
|
"@radix-ui/react-dialog": "^1.1.2",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.1",
|
"@radix-ui/react-navigation-menu": "^1.2.1",
|
||||||
@ -20,57 +19,49 @@
|
|||||||
"@radix-ui/react-progress": "^1.1.0",
|
"@radix-ui/react-progress": "^1.1.0",
|
||||||
"@radix-ui/react-separator": "^1.1.0",
|
"@radix-ui/react-separator": "^1.1.0",
|
||||||
"@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.3",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
"@typescript-eslint/eslint-plugin": "^8.10.0",
|
||||||
"caniuse-lite": "^1.0.30001680",
|
"caniuse-lite": "^1.0.30001669",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"country-flag-icons": "^1.5.13",
|
"country-flag-icons": "^1.5.13",
|
||||||
"dotted-map": "^2.2.3",
|
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"flag-icons": "^7.2.3",
|
"flag-icons": "^7.2.3",
|
||||||
"framer-motion": "^12.0.0-alpha.1",
|
"framer-motion": "^11.11.9",
|
||||||
"lucide-react": "^0.454.0",
|
"lucide-react": "^0.451.0",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"next": "^15.0.3",
|
"next": "^14.2.15",
|
||||||
"next-auth": "^5.0.0-beta.25",
|
"next-auth": "^5.0.0-beta.25",
|
||||||
"next-intl": "^3.25.1",
|
"next-intl": "^3.21.1",
|
||||||
"next-runtime-env": "^3.2.2",
|
"next-runtime-env": "^3.2.2",
|
||||||
"next-themes": "^0.4.3",
|
"next-themes": "^0.3.0",
|
||||||
"react": "19.0.0-rc-02c0e824-20241028",
|
"react": "^18.3.1",
|
||||||
"react-device-detect": "^2.2.3",
|
"react-device-detect": "^2.2.3",
|
||||||
"react-dom": "19.0.0-rc-02c0e824-20241028",
|
"react-dom": "^18.3.1",
|
||||||
"react-intersection-observer": "^9.13.1",
|
"react-intersection-observer": "^9.13.1",
|
||||||
"react-wrap-balancer": "^1.1.1",
|
"react-wrap-balancer": "^1.1.1",
|
||||||
"recharts": "^2.13.3",
|
"recharts": "2.12.7",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"swr": "^2.2.6-beta.4",
|
"swr": "^2.2.6-beta.4",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7"
|
||||||
"typescript-eslint": "^8.15.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint-plugin-turbo": "^2.3.0",
|
"eslint-plugin-turbo": "^2.2.1",
|
||||||
"eslint-plugin-unused-imports": "^4.1.4",
|
"eslint-plugin-unused-imports": "^4.1.4",
|
||||||
"@next/bundle-analyzer": "^15.0.3",
|
"@next/bundle-analyzer": "^14.2.15",
|
||||||
"@types/node": "^22.9.0",
|
"@types/node": "^22.7.7",
|
||||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
"@types/react": "^18.3.11",
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.15.0",
|
"eslint": "^9.13.0",
|
||||||
"eslint-config-next": "^15.0.3",
|
"eslint-config-next": "^14.2.15",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.47",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.8",
|
"prettier-plugin-tailwindcss": "^0.6.8",
|
||||||
"tailwindcss": "^3.4.15",
|
"tailwindcss": "^3.4.14",
|
||||||
"typescript": "^5.6.3"
|
"typescript": "^5.6.3"
|
||||||
},
|
}
|
||||||
"overrides": {
|
|
||||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
|
||||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
|
||||||
"react-is": "^19.0.0-rc-69d4b800-20241021"
|
|
||||||
},
|
|
||||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "NezhaDash PWA App",
|
"name": "HomeDash",
|
||||||
"short_name": "NezhaDash",
|
"short_name": "HomeDash PWA App",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/android-chrome-192x192.png",
|
"src": "/android-chrome-192x192.png",
|
||||||
|
1638
styles/flag-icons.min.css
vendored
Normal file
1638
styles/flag-icons.min.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 98%;
|
--background: 0 0% 100%;
|
||||||
--foreground: 20 14.3% 4.1%;
|
--foreground: 20 14.3% 4.1%;
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
--card-foreground: 20 14.3% 4.1%;
|
--card-foreground: 20 14.3% 4.1%;
|
||||||
@ -37,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 30 15% 8%;
|
--background: 20 14.3% 4.1%;
|
||||||
--foreground: 60 9.1% 97.8%;
|
--foreground: 60 9.1% 97.8%;
|
||||||
--card: 20 14.3% 4.1%;
|
--card: 20 14.3% 4.1%;
|
||||||
--card-foreground: 60 9.1% 97.8%;
|
--card-foreground: 60 9.1% 97.8%;
|
||||||
|
Loading…
Reference in New Issue
Block a user