Repository: hamster1963/HomeDash
Branch: main
Commit: abb6f8e8b9bf
Files: 54
Total size: 710.4 KB
Directory structure:
gitextract_1c6wmyk2/
├── .eslintrc.cjs
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── DeployTo_ghrc_ali.yml
│ └── autoMerge.yml
├── .gitignore
├── .idea/
│ ├── .gitignore
│ ├── hamster-home.iml
│ ├── inspectionProfiles/
│ │ └── Project_Default.xml
│ ├── modules.xml
│ └── vcs.xml
├── Dockerfile
├── LICENSE
├── README.md
├── app/
│ ├── home/
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── main/
│ │ │ ├── components/
│ │ │ │ ├── NewDescription.tsx
│ │ │ │ ├── infoCard.tsx
│ │ │ │ ├── networkChart.tsx
│ │ │ │ ├── networkSummary.tsx
│ │ │ │ ├── proxySummary.tsx
│ │ │ │ └── serverList.tsx
│ │ │ ├── dashboard.tsx
│ │ │ └── page.tsx
│ │ ├── semi.css
│ │ ├── service/
│ │ │ ├── components/
│ │ │ │ ├── homeSummary.tsx
│ │ │ │ ├── paySummary.tsx
│ │ │ │ ├── serviceList.tsx
│ │ │ │ └── serviceSummary.tsx
│ │ │ ├── page.tsx
│ │ │ └── service.tsx
│ │ ├── style.css
│ │ ├── surge/
│ │ │ ├── components/
│ │ │ │ └── surgeCard.tsx
│ │ │ ├── page.tsx
│ │ │ ├── surge.tsx
│ │ │ ├── surgeStatus.tsx
│ │ │ └── surgeTraffic.tsx
│ │ ├── utils/
│ │ │ ├── fixedButton.tsx
│ │ │ ├── functions.tsx
│ │ │ ├── leftSide.tsx
│ │ │ ├── sseContext.tsx
│ │ │ └── sseFetch.tsx
│ │ └── xui/
│ │ ├── components/
│ │ │ ├── xuiDetail.tsx
│ │ │ └── xuiTable.tsx
│ │ ├── page.tsx
│ │ └── xui.tsx
│ ├── layout.tsx
│ └── providers.tsx
├── env.d.ts
├── git-cliff/
│ └── cliff.toml
├── next.config.js
├── package.json
├── tsconfig.json
├── turbo.json
└── vercel.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.cjs
================================================
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path')
/** @type {import("eslint").Linter.Config} */
const config = {
overrides: [
{
extends: [
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
files: ['*.ts', '*.tsx'],
parserOptions: {
project: path.join(__dirname, 'tsconfig.json'),
},
rules: {
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
},
},
],
parser: '@typescript-eslint/parser',
parserOptions: {
project: path.join(__dirname, 'tsconfig.json'),
},
ignorePatterns: ['**/*.md'],
plugins: [
'turbo',
'@typescript-eslint',
'simple-import-sort',
'unused-imports',
],
extends: ['next/core-web-vitals', 'plugin:@typescript-eslint/recommended'],
rules: {
'@typescript-eslint/consistent-type-imports': [
'warn',
{
prefer: 'type-imports',
fixStyle: 'inline-type-imports',
},
],
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'warn',
'unused-imports/no-unused-imports': 'error',
},
}
module.exports = config
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- package-ecosystem: "github-actions" # GitHub Actions
directory: "/"
schedule:
interval: "daily"
================================================
FILE: .github/workflows/DeployTo_ghrc_ali.yml
================================================
name: Build and push Docker image
on:
push:
tags:
- 'v*'
jobs:
changelog:
name: Generate Changelog
runs-on: ubuntu-latest
outputs:
release_body: ${{ steps.git-cliff.outputs.content }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate a changelog
uses: orhun/git-cliff-action@v3
id: git-cliff
with:
config: git-cliff/cliff.toml
args: -vv --latest --strip 'footer'
env:
OUTPUT: CHANGES.md
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
body: ${{ steps.git-cliff.outputs.content }}
token: ${{ secrets.GITHUB_TOKEN }}
env:
GITHUB_REPOSITORY: ${{ github.repository }}
build-and-push:
runs-on: ubuntu-latest
needs: changelog
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to AliYun Container Registry
uses: docker/login-action@v3
with:
registry: registry.cn-guangzhou.aliyuncs.com
username: ${{ secrets.ALI_USERNAME }}
password: ${{ secrets.ALI_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ github.repository }}
registry.cn-guangzhou.aliyuncs.com/hamster-home/homedash
tags: |
type=raw,value=latest
type=ref,event=tag
- name: Print environment variables
run: |
echo "${{env.GIT_COMMIT_LOG}}"
echo "${{env.BUILD_TIME}}"
echo "${{env.CURRENT_VERSION}}"
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GIT_TAG=${{env.CURRENT_VERSION}}
GIT_COMMIT_LOG=${{env.GIT_COMMIT_LOG}}
BUILD_TIME=${{env.BUILD_TIME}}
PROD_ENV=${{secrets.PROD_ENV}}
================================================
FILE: .github/workflows/autoMerge.yml
================================================
name: Dependabot auto-merge
on: pull_request
permissions:
contents: write
pull-requests: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Enable auto-merge for Dependabot PRs
if: ${{contains(steps.metadata.outputs.dependency-names, 'my-dependency') && steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
.env
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# turbo
.turbo
================================================
FILE: .idea/.gitignore
================================================
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# GitHub Copilot persisted chat sessions
/copilot/chatSessions
================================================
FILE: .idea/hamster-home.iml
================================================
================================================
FILE: .idea/inspectionProfiles/Project_Default.xml
================================================
================================================
FILE: .idea/modules.xml
================================================
================================================
FILE: .idea/vcs.xml
================================================
================================================
FILE: Dockerfile
================================================
FROM node:18-alpine AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ARG PROD_ENV=""
# Appends to .env.production
RUN printf "$PROD_ENV" >> .env.production
RUN yarn build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 仓鼠
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
项目因技术栈选择问题已归档,重构中...
新重构 HomeDash 地址: https://dash.buycoffee.top

HomeDash
HomeDash 是一个基于 Next.js 和 Semi-design 的仪表盘
Demo地址: https://home.buycoffee.top


## 一键部署前端
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fhamster1963%2FHomeDash%3Ftab%3Dreadme-ov-file&env=NEXT_PUBLIC_GO_API_BASE_URL&envDescription=%E5%A1%AB%E5%85%A5%E5%90%8E%E7%AB%AF%20API%20%E5%9C%B0%E5%9D%80)
* 在环境变量中填入后端 API 地址
### 文档完善中...
## 后端仓库
[HomeDash-Backend](https://github.com/hamster1963/HomeDash-Backend)
## 本地开发
### 配置文件填入后端地址
```bash
cp .env.example .env.local
```
### 安装依赖
```bash
pnpm install
```
### 运行前端
```bash
pnpm dev
```
## 同时也兼容了黑色模式!


================================================
FILE: app/home/globals.css
================================================
/* http://meyerweb.com/eric/tools/css/reset/
v5.0.1 | 20191019
License: none (public domain)
*/
@import '@/app/home/semi.css';
@import "@/app/home/style.css";
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
main, menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font: inherit;
vertical-align: baseline;
/* Disable tap highlights on iOS */
-webkit-tap-highlight-color: transparent;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, main, menu, nav, section {
display: block;
}
/* HTML5 hidden-attribute fix for newer browsers */
*[hidden] {
display: none;
}
body {
line-height: 1;
color: var(--semi-color-text-0);
background-color: var( --semi-color-bg-0);
}
menu, ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
================================================
FILE: app/home/layout.tsx
================================================
"use client";
import "./globals.css";
import { Layout } from "@douyinfe/semi-ui";
import React, { useEffect, useState } from "react";
import FixedButton from "@/app/home/utils/fixedButton";
import LeftSide from "@/app/home/utils/leftSide";
import { useSSEContext } from "@/app/home/utils/sseContext";
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const { Sider, Content } = Layout;
// 从 SSEContext 中获取侧边栏状态及其 setter 函数
const { isNavCollapsed } = useSSEContext();
// 避免页面初始化时侧边栏闪烁的问题
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
return (
{children}
);
}
================================================
FILE: app/home/main/components/NewDescription.tsx
================================================
import { Typography } from "@douyinfe/semi-ui";
import React from "react";
type NewDescriptionProps = {
keyString: string;
value: React.ReactNode;
style?: React.CSSProperties;
};
export default function NewDescription({
keyString,
value,
style,
}: NewDescriptionProps) {
const { Text, Title } = Typography;
return (
{keyString}
{value}
);
}
================================================
FILE: app/home/main/components/infoCard.tsx
================================================
import {
IconBolt,
IconBrackets,
IconCalendarClock,
IconConnectionPoint2,
IconGithubLogo,
IconGlobeStroke,
IconUser,
} from "@douyinfe/semi-icons";
import { Card, Progress, Typography } from "@douyinfe/semi-ui";
import React from "react";
import { z } from "zod";
import { SSEDataFetch } from "@/app/home/utils/sseFetch";
type InfoCardProps = {
backgroundColor: string;
backgroundFillColor: string;
icon: React.ReactNode;
title: string;
moreIcon: React.ReactNode;
moreInfo: string;
value: number;
unit: string;
name: string;
total: number;
};
export function InfoCard(props: InfoCardProps) {
const { Title } = Typography;
// 计算进度百分比
const progressPercent =
props.value !== 0 ? Math.round((props.value / props.total) * 100) : 0;
// 使用线性渐变设置背景色
// 可以根据需要更改颜色
const backgroundColor = `linear-gradient(90deg, ${props.backgroundFillColor} ${progressPercent}%, ${props.backgroundColor} ${progressPercent}%)`;
return (
{props.icon}
{props.title}
{props.moreInfo}
{props.moreIcon}
{props.value.toFixed(2)}
{props.unit}
{props.name}
{props.value.toFixed(2)}/{props.total}
);
}
const CoffeeInfoSchema = z.object({
usedBound: z.string(),
remainBound: z.string(),
planBound: z.string(),
resetDay: z.string(),
});
const XuiInfoSchema = z.object({
user_count: z.number(),
up_total: z.number(),
down_total: z.number(),
});
const GitHubInfoSchema = z.object({
included_minutes: z.number(),
next_bill_day: z.number(),
total_minutes_used: z.number(),
});
const BoceCountSchema = z.object({
boce_count: z.number(),
});
export default function InfoCardList() {
const coffeeGetData = SSEDataFetch(
process.env.NEXT_PUBLIC_GO_API_BASE_URL + "/GetNetworkDataSSE",
);
const coffeeValidation = CoffeeInfoSchema.safeParse(
coffeeGetData?.coffeeInfo,
);
const xuiGetData = SSEDataFetch(
process.env.NEXT_PUBLIC_GO_API_BASE_URL + "/GetXuiDataSSE",
);
const xuiValidation = XuiInfoSchema.safeParse(xuiGetData?.xuiData);
const githubGetData = SSEDataFetch(
process.env.NEXT_PUBLIC_GO_API_BASE_URL + "/GetGitHubActionDataSSE",
);
const githubValidation = GitHubInfoSchema.safeParse(
githubGetData?.GitHubActionData,
);
const boceCountData = SSEDataFetch(
process.env.NEXT_PUBLIC_GO_API_BASE_URL + "/GetBoceCountSSE",
);
const boceCountValidation = BoceCountSchema.safeParse(boceCountData);
return (
<>
}
title={"代理服务"}
moreIcon={}
value={
coffeeValidation.success ? Number(coffeeValidation.data.usedBound) : 0
}
unit={"GB"}
name={"CoffeeCloud"}
total={
coffeeValidation.success ? Number(coffeeValidation.data.planBound) : 0
}
moreInfo={
"重置: " +
(coffeeValidation.success ? coffeeValidation.data.resetDay : 0) +
"天"
}
/>
}
title={"x-ui 面板"}
moreIcon={}
value={
xuiValidation.success
? xuiValidation.data.down_total + xuiValidation.data.up_total
: 0
}
unit={"GB"}
name={"已用流量"}
total={xuiValidation.success ? 1000 : 0}
moreInfo={
"用户数: " +
(xuiValidation.success ? xuiValidation.data.user_count : 0)
}
/>
}
title={"Actions"}
moreIcon={}
value={
githubValidation.success
? githubValidation.data.total_minutes_used
: 0
}
unit={"Minute"}
name={"已用构建时间"}
total={
githubValidation.success ? githubValidation.data.included_minutes : 0
}
moreInfo={
"重置: " +
(githubValidation.success ? githubValidation.data.next_bill_day : 0) +
"天"
}
/>
}
title={"拨测总数"}
moreIcon={}
value={
boceCountValidation.success ? boceCountValidation.data.boce_count : 0
}
unit={"Count"}
name={"任务数"}
total={boceCountValidation.success ? 10000 : 0}
moreInfo={"拨测服务"}
/>
>
);
}
================================================
FILE: app/home/main/components/networkChart.tsx
================================================
import React from "react";
import { Area, AreaChart, ResponsiveContainer, YAxis } from "recharts";
type NetworkChartProps = {
data: any;
keyString: string;
colorToken: string;
};
export default function NetworkChart({
data,
keyString,
colorToken,
}: NetworkChartProps) {
const backgroundColor = `rgba(var(--semi-${colorToken}-0), 1)`;
const strokeColor = `rgba(var(--semi-${colorToken}-5), 1)`;
const fill = `url(#${colorToken})`;
return (
);
}
================================================
FILE: app/home/main/components/networkSummary.tsx
================================================
import { Skeleton } from "@douyinfe/semi-ui";
import React, { useEffect } from "react";
import { z } from "zod";
import NetworkChart from "@/app/home/main/components/networkChart";
import NewDescription from "@/app/home/main/components/NewDescription";
import { useSSEContext } from "@/app/home/utils/sseContext";
import { SSEDataFetch } from "@/app/home/utils/sseFetch";
const NetworkInfoSchema = z.object({
deviceCount: z.number(),
rxSpeedMbps: z.number(),
txSpeedMbps: z.number(),
})
const AdGuardInfoSchema = z.object({
AvgProcessingTime: z.number(),
NumBlockedFiltering: z.number(),
NumDnsQueries: z.number(),
});
export default function NetworkSummary() {
const networkGetData = SSEDataFetch(
process.env.NEXT_PUBLIC_GO_API_BASE_URL + "/GetNetworkDataSSE",
);
const networkValidation = NetworkInfoSchema.safeParse(
networkGetData?.homeNetwork,
);
const adguardGetData = SSEDataFetch(
process.env.NEXT_PUBLIC_GO_API_BASE_URL + "/GetAdGuardInfoSSE",
);
const adguardValidation = AdGuardInfoSchema.safeParse(
adguardGetData?.adGuardInfo,
);
const { setSSEConnect, HomeNetworkSpeedList, setHomeNetworkSpeedList } =
useSSEContext();
useEffect(() => {
if (networkValidation.success) {
// 获取当前的速度列表
const currentList = [...HomeNetworkSpeedList];
// 添加新的速度数据到列表中
currentList.push({
speed: Number(
networkValidation.data.rxSpeedMbps +
networkValidation.data.txSpeedMbps,
),
});
// 如果长度大于10,删除第一个元素
if (currentList.length > 10) {
currentList.shift();
}
// 更新速度列表
setHomeNetworkSpeedList(currentList);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [networkGetData]);
useEffect(() => {
if (networkValidation.success) {
setSSEConnect(true);
} else {
setSSEConnect(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [networkGetData]);
const placeholder = (
);
return (
<>
)
}
/>
)
}
/>
)
}
/>
>
);
}
================================================
FILE: app/home/main/components/proxySummary.tsx
================================================
import { Skeleton, Typography } from "@douyinfe/semi-ui";
import React, { useEffect } from "react";
import { z } from "zod";
import NetworkChart from "@/app/home/main/components/networkChart";
import NewDescription from "@/app/home/main/components/NewDescription";
import { useSSEContext } from "@/app/home/utils/sseContext";
import { SSEDataFetch } from "@/app/home/utils/sseFetch";
const proxyInfoSchema = z.object({
rxSpeedMbps: z.number(),
txSpeedMbps: z.number(),
});
const nodeInfoSchema = z.object({
nodeName: z.string(),
nodeLatency: z.string(),
});
export default function ProxySummary() {
const { Title } = Typography;
const proxyGetData = SSEDataFetch(
process.env.NEXT_PUBLIC_GO_API_BASE_URL + "/GetNetworkDataSSE",
);
const proxyValidation = proxyInfoSchema.safeParse(proxyGetData?.proxyNetwork);
const nodeValidation = nodeInfoSchema.safeParse(proxyGetData?.nodeInfo);
const { ProxyNetworkSpeedList, setProxyNetworkSpeedList } = useSSEContext();
useEffect(() => {
if (proxyValidation.success) {
// 获取当前的速度列表
const currentList = [...ProxyNetworkSpeedList];
// 添加新的速度数据到列表中
currentList.push({
speed: Number(
proxyValidation.data.rxSpeedMbps + proxyValidation.data.txSpeedMbps,
),
});
// 如果长度大于10,删除第一个元素
if (currentList.length > 10) {
currentList.shift();
}
// 更新速度列表
setProxyNetworkSpeedList(currentList);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [proxyGetData]);
const placeholder = (
);
return (
<>
{nodeValidation.data.nodeName.slice(1, -1)}
) : (
)
}
/>
)
}
/>
)
}
/>
)
}
/>
>
);
}
================================================
FILE: app/home/main/components/serverList.tsx
================================================
import { Progress, Skeleton } from "@douyinfe/semi-ui";
import React from "react";
import NewDescription from "@/app/home/main/components/NewDescription";
import { SSEDataFetch } from "@/app/home/utils/sseFetch";
type ServerCardProps = {
id: number;
name: string;
status: string;
cpu: number;
memory: number;
disk: number;
uptime: number;
up: number;
down: number;
};
const placeholder = (
);
export function ServerCard(props: ServerCardProps) {
return (
<>
}
/>
) : (
)
}
/>