feat(node): Add NodeStatus

This commit is contained in:
web@ppanel 2024-12-13 14:51:55 +07:00
parent d0be6854e6
commit c712624930
7 changed files with 181 additions and 42 deletions

View File

@ -0,0 +1,94 @@
'use client';
import { formatDate } from '@repo/ui/utils';
import { Badge } from '@shadcn/ui/badge';
import { Progress } from '@shadcn/ui/progress';
import { ScrollArea } from '@shadcn/ui/scroll-area';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@shadcn/ui/tooltip';
import { useTranslations } from 'next-intl';
export function formatPercentage(value: number): string {
return `${(value * 100).toFixed(1)}%`;
}
export function NodeStatusCell({ status }: { status: API.NodeStatus }) {
const t = useTranslations('server.node');
const {
last_at,
online_users,
status: serverStatus,
} = status || {
online_users: [],
status: {
cpu: 0,
mem: 0,
disk: 0,
updated_at: 0,
},
last_at: 0,
};
const isOnline = last_at > 0;
const badgeVariant = isOnline ? 'default' : 'destructive';
const badgeText = isOnline ? t('normal') : t('abnormal');
const onlineCount = Array.isArray(online_users) ? online_users.length : 0;
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className='flex items-center space-x-2 rounded-md'>
<Badge variant={badgeVariant}>{badgeText}</Badge>
<span className='text-sm font-medium'>
{t('onlineCount')}: {onlineCount}
</span>
</div>
</TooltipTrigger>
<TooltipContent className='bg-muted text-foreground w-80'>
<div className='space-y-4'>
<>
<div className='space-y-2'>
<div className='space-y-1'>
<div className='flex justify-between text-xs'>
<span className='font-medium'>CPU</span>
<span>{formatPercentage(serverStatus?.cpu ?? 0)}</span>
</div>
<Progress value={(serverStatus?.cpu ?? 0) * 100} className='h-2' />
</div>
<div className='space-y-1'>
<div className='flex justify-between text-xs'>
<span className='font-medium'>{t('memory')}</span>
<span>{formatPercentage(serverStatus?.mem ?? 0)}</span>
</div>
<Progress value={(serverStatus?.mem ?? 0) * 100} className='h-2' />
</div>
<div className='space-y-1'>
<div className='flex justify-between text-xs'>
<span className='font-medium'>{t('disk')}</span>
<span>{formatPercentage(serverStatus?.disk ?? 0)}</span>
</div>
<Progress value={(serverStatus?.disk ?? 0) * 100} className='h-2' />
</div>
</div>
<div className='text-xs'>
{t('lastUpdated')}: {formatDate(serverStatus?.updated_at ?? 0)}
</div>
{isOnline && online_users.length > 0 && (
<div className='space-y-2'>
<h4 className='text-sm font-semibold'>{t('onlineUsers')}</h4>
<ScrollArea className='h-[400px] rounded-md border p-2'>
{online_users.map((user, index) => (
<div key={user.uid} className='py-1 text-xs'>
{user.ip} (UID: {user.uid})
</div>
))}
</ScrollArea>
</div>
)}
</>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}

View File

@ -21,6 +21,7 @@ import { useQuery } from '@tanstack/react-query';
import { useTranslations } from 'next-intl';
import { useRef, useState } from 'react';
import NodeForm from './node-form';
import { NodeStatusCell } from './node-status';
export default function NodeTable() {
const t = useTranslations('server.node');
@ -109,23 +110,11 @@ export default function NodeTable() {
);
},
},
{
accessorKey: 'last',
accessorKey: 'status',
header: t('status'),
cell: ({ row }) => {
const { last } = row.original;
return last ? (
<>
<Badge>{t('normal')}</Badge>
<span className='ml-2'>
{t('onlineCount')}: {last.count}
</span>
</>
) : (
<Badge variant='destructive'>{t('abnormal')}</Badge>
);
return <NodeStatusCell status={row.original?.status} />;
},
},
{

View File

@ -40,6 +40,10 @@
"edit": "编辑",
"editNode": "编辑节点",
"enable": "启用",
"memory": "内存",
"disk": "磁盘",
"lastUpdated": "最后更新",
"onlineUsers": "在线用户",
"form": {
"allowInsecure": "允许不安全",
"cancel": "取消",

View File

@ -487,6 +487,17 @@ declare namespace API {
node_push_interval: number;
};
type NodeStatus = {
online_users: OnlineUser[];
status: ServerStatus;
last_at: number;
};
type OnlineUser = {
uid: number;
ip: string;
};
type Order = {
id: number;
user_id: number;
@ -541,11 +552,6 @@ declare namespace API {
enable: boolean;
};
type Push = {
push_at: number;
count: number;
};
type RegisterConfig = {
stop_register: boolean;
enable_trial: boolean;
@ -590,7 +596,7 @@ declare namespace API {
enable: boolean;
created_at: number;
updated_at: number;
last: Push;
status: NodeStatus;
};
type ServerGroup = {
@ -601,6 +607,13 @@ declare namespace API {
updated_at: number;
};
type ServerStatus = {
cpu: number;
mem: number;
disk: number;
updated_at: number;
};
type Shadowsocks = {
method: string;
port: number;
@ -687,7 +700,7 @@ declare namespace API {
type TransportConfig = {
path: string;
host: string;
server_name: string;
service_name: string;
};
type Trojan = {

View File

@ -139,6 +139,17 @@ declare namespace API {
node_push_interval: number;
};
type NodeStatus = {
online_users: OnlineUser[];
status: ServerStatus;
last_at: number;
};
type OnlineUser = {
uid: number;
ip: string;
};
type Order = {
id: number;
user_id: number;
@ -193,11 +204,6 @@ declare namespace API {
enable: boolean;
};
type Push = {
push_at: number;
count: number;
};
type RegisterConfig = {
stop_register: boolean;
enable_trial: boolean;
@ -258,7 +264,7 @@ declare namespace API {
enable: boolean;
created_at: number;
updated_at: number;
last: Push;
status: NodeStatus;
};
type ServerGroup = {
@ -269,6 +275,13 @@ declare namespace API {
updated_at: number;
};
type ServerStatus = {
cpu: number;
mem: number;
disk: number;
updated_at: number;
};
type Shadowsocks = {
method: string;
port: number;
@ -351,7 +364,7 @@ declare namespace API {
type TransportConfig = {
path: string;
host: string;
server_name: string;
service_name: string;
};
type Trojan = {

View File

@ -139,6 +139,17 @@ declare namespace API {
node_push_interval: number;
};
type NodeStatus = {
online_users: OnlineUser[];
status: ServerStatus;
last_at: number;
};
type OnlineUser = {
uid: number;
ip: string;
};
type Order = {
id: number;
user_id: number;
@ -193,11 +204,6 @@ declare namespace API {
enable: boolean;
};
type Push = {
push_at: number;
count: number;
};
type RegisterConfig = {
stop_register: boolean;
enable_trial: boolean;
@ -258,7 +264,7 @@ declare namespace API {
enable: boolean;
created_at: number;
updated_at: number;
last: Push;
status: NodeStatus;
};
type ServerGroup = {
@ -269,6 +275,13 @@ declare namespace API {
updated_at: number;
};
type ServerStatus = {
cpu: number;
mem: number;
disk: number;
updated_at: number;
};
type Shadowsocks = {
method: string;
port: number;
@ -351,7 +364,7 @@ declare namespace API {
type TransportConfig = {
path: string;
host: string;
server_name: string;
service_name: string;
};
type Trojan = {

View File

@ -156,6 +156,17 @@ declare namespace API {
node_push_interval: number;
};
type NodeStatus = {
online_users: OnlineUser[];
status: ServerStatus;
last_at: number;
};
type OnlineUser = {
uid: number;
ip: string;
};
type Order = {
id: number;
user_id: number;
@ -234,11 +245,6 @@ declare namespace API {
order_no: string;
};
type Push = {
push_at: number;
count: number;
};
type QueryAnnouncementParams = {
page: number;
size: number;
@ -391,7 +397,7 @@ declare namespace API {
enable: boolean;
created_at: number;
updated_at: number;
last: Push;
status: NodeStatus;
};
type ServerGroup = {
@ -402,6 +408,13 @@ declare namespace API {
updated_at: number;
};
type ServerStatus = {
cpu: number;
mem: number;
disk: number;
updated_at: number;
};
type Shadowsocks = {
method: string;
port: number;
@ -490,7 +503,7 @@ declare namespace API {
type TransportConfig = {
path: string;
host: string;
server_name: string;
service_name: string;
};
type Trojan = {